/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
 * Copyright (C) 2005 Tim Azzopardi <tim@tigerfive.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.lang {

import asruby.Ruby;
import asruby.runtime.Block;
import asruby.runtime.CallbackFactory;
import asruby.runtime.ClassIndex;
import asruby.runtime.Frame;
import asruby.runtime.IRubyObject;
import asruby.runtime.MethodIndex;
import asruby.runtime.ThreadContext;

/**
 * Implementation of Ruby String class
 * 
 * Concurrency: no synchronization is required among readers, but
 * all users must synchronize externally with writers.
 *
 */
public class RubyString extends RubyObject {
    //private static var ASCII: ASCIIEncoding =  ASCIIEncoding.INSTANCE;

    // string doesn't share any resources
    private static var SHARE_LEVEL_NONE: int =  0;
    // string has it's own ByteList, but it's pointing to a shared buffer (byte[])
    private static var SHARE_LEVEL_BUFFER: int =  1;
    // string doesn't have it's own ByteList (values)
    private static var SHARE_LEVEL_BYTELIST: int =  2;

    private  var shareLevel: int =  SHARE_LEVEL_NONE;

    private  var value:String;

    private static var STRING_ALLOCATOR:Function =
        function allocate(runtime:Ruby, klass:RubyClass):IRubyObject{
            var newString: RubyString =  runtime.newString(null);
            
            newString.setMetaClass(klass);
            
            return newString;
        };
    
    public static function createStringClass(runtime:Ruby):RubyClass{
        var stringClass: RubyClass =  runtime.defineClass("String", runtime.getObject(), STRING_ALLOCATOR);
        runtime.setString(stringClass);
        stringClass.index = ClassIndex.STRING;
        stringClass.kindOf = 
                function isKindOf(obj:IRubyObject, type:RubyModule):Boolean{
                    return obj instanceof RubyString;
                };

        var callbackFactory: CallbackFactory =  runtime.callbackFactory(RubyString);
        
/*         stringClass.includeModule(runtime.getComparable());
        stringClass.includeModule(runtime.getEnumerable()); */
        
        stringClass.defineAnnotatedMethods(RubyString);
        stringClass.dispatcher = callbackFactory.createDispatcher(stringClass);
        
        return stringClass;
    }

    // short circuit for String key comparison
    public override function eql(other:IRubyObject):Boolean{
        if (other.getMetaClass() == getRuntime().getString()) 
        	return value == RubyString(other).value;
        return super.eql(other);
    }
/*
    function RubyString(runtime:Ruby, rubyClass:RubyClass, value:CharSequence){
        super(runtime, rubyClass);
        //assert value != null;
        this.value = new ByteList(ByteList.plain(value),false);
    }

    function RubyString(runtime:Ruby, rubyClass:RubyClass, value:Array){
        super(runtime, rubyClass);
        //assert value != null;
        this.value = new ByteList(value);
    }

    function RubyString(runtime:Ruby, rubyClass:RubyClass, value:ByteList){
        super(runtime, rubyClass);
        //assert value != null;
        this.value = value;
    }
    
    function RubyString(runtime:Ruby, rubyClass:RubyClass, value:ByteList, objectSpace:Boolean){
        super(runtime, rubyClass, objectSpace);
        //assert value != null;
        this.value = value;
    }    

    public  function getNativeTypeIndex():int{
        return ClassIndex.STRING;
    }

    public  function getJavaClass():Class{
        return String;
    }

    public  function convertToString():RubyString{
        return this;
    }

    public  function toString():String{
        return value.toString();
    }

    // rb_str_dup
    public  function strDup():RubyString{
        return strDup(getMetaClass());
    }

    function strDup(clazz:RubyClass):RubyString{
        shareLevel = SHARE_LEVEL_BYTELIST;
        var dup: RubyString =  new RubyString(getRuntime(), clazz, value);
        dup.shareLevel = SHARE_LEVEL_BYTELIST;

        dup.infectBy(this);
        return dup;
    }    

    public  function makeShared(index:int, len:int):RubyString{
        if (len == 0) {
            var s: RubyString =  newEmptyString(getRuntime(), getMetaClass());
            s.infectBy(this);
            return s;
        }

        if (shareLevel == SHARE_LEVEL_NONE) shareLevel = SHARE_LEVEL_BUFFER;
        var shared: RubyString =  new RubyString(getRuntime(), getMetaClass(), value.makeShared(index, len));
        shared.shareLevel = SHARE_LEVEL_BUFFER;

        shared.infectBy(this);
        return shared;
    }

    private  function modifyCheck():void{
        if ((flags & FROZEN_F) != 0) throw getRuntime().newFrozenError("string" + getMetaClass().getName());           

        if (!isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify string");
        }
    }
    
    private  function modifyCheck(b:Array, len:int):void{
        if (value.bytes != b || value.realSize != len) throw getRuntime().newRuntimeError("string modified");
    }
    
    private  function frozenCheck():void{
        if (isFrozen()) throw getRuntime().newRuntimeError("string frozen");
    }

    // rb_str_modify
    public  function modify():void{
        modifyCheck();

        if (shareLevel != SHARE_LEVEL_NONE) {
            if (shareLevel == SHARE_LEVEL_BYTELIST) {
                value = value.dup();
            } else {
                value.unshare();
            }
            shareLevel = SHARE_LEVEL_NONE;
        }

        value.invalidate();
    }

    // rb_str_modify (with length bytes ensured)   
    public  function modify(length:int):void{
        modifyCheck();

        if (shareLevel != SHARE_LEVEL_NONE) {
            if (shareLevel == SHARE_LEVEL_BYTELIST) {
                value = value.dup(length);
            } else {
                value.unshare(length);
            }
            shareLevel = SHARE_LEVEL_NONE;
        } else {
            value.ensure(length);
        }

        value.invalidate();
    }        
    
    private  function view(bytes:ByteList):void{
        modifyCheck();

        value = bytes;
        shareLevel = SHARE_LEVEL_NONE;
    }

    private function view(bytes:String):void {
        modifyCheck();        

        value.replace(bytes);
        shareLevel = SHARE_LEVEL_NONE;

        value.invalidate();        
    }

    private  function view(index:int, len:int):void{
        modifyCheck();

        if (shareLevel != SHARE_LEVEL_NONE) {
            if (shareLevel == SHARE_LEVEL_BYTELIST) {
                // if len == 0 then shared empty
                value = value.makeShared(index, len);
                shareLevel = SHARE_LEVEL_BUFFER;
            } else {
                value.view(index, len);
            }
        } else {        
            value.view(index, len);
            // FIXME this below is temporary, but its much safer for COW (it prevents not shared Strings with begin != 0)
            // this allows now e.g.: ByteList#set not to be begin aware
            shareLevel = SHARE_LEVEL_BUFFER;
        }

        value.invalidate();
    }

    public static function bytesToString(bytes:Array, beg:int, len:int):String{
        return new String(ByteList.plain(bytes, beg, len));
    }

    public static function byteListToString(bytes:ByteList):String{
        return bytesToString(bytes.unsafeBytes(), bytes.begin(), bytes.length());
    }

    public static function bytesToString(bytes:Array):String{
        return bytesToString(bytes, 0, bytes.length);
    }

    public static function stringToBytes(string:String):Array{
        return ByteList.plain(string);
    }

    public static function isDigit(c:int):Boolean{
        return c >= '0' && c <= '9';
    }

    public static function isUpper(c:int):Boolean{
        return c >= 'A' && c <= 'Z';
    }

    public static function isLower(c:int):Boolean{
        return c >= 'a' && c <= 'z';
    }

    public static function isLetter(c:int):Boolean{
        return isUpper(c) || isLower(c);
    }

    public static function isAlnum(c:int):Boolean{
        return isUpper(c) || isLower(c) || isDigit(c);
    }

    public static function isPrint(c:int):Boolean{
        return c >= 0x20 && c <= 0x7E;
    }

    public  function asString():RubyString{
        return this;
    }

    public  function checkStringType():IRubyObject{
        return this;
    }

    public  function to_s():IRubyObject{
        if (getMetaClass().getRealClass() != getRuntime().getString()) {
            return strDup(getRuntime().getString());
        }
        return this;
    }

    // rb_str_cmp_m 
    public  function op_cmp(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyString) {
            return getRuntime().newFixnum(op_cmpRubyString(other));
        }

        // deal with case when "other" is not a string
        if (other.respondsTo("to_str") && other.respondsTo("<=>")) {
            var result: IRubyObject =  other.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", this);

            if (result instanceof RubyNumeric) {
                return RubyNumeric(result).op_uminus(context);
            }
        }

        return getRuntime().getNil();
    }
        
    public  function op_equal(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyString)) {
            if (!other.respondsTo("to_str")) return getRuntime().getFalse();
            var runtime: Ruby =  getRuntime();
            return other.callMethod(context, MethodIndex.EQUALEQUAL, "==", this).isTrue() ? runtime.getTrue() : runtime.getFalse();
        }
        return value.equal(RubyString(other).value) ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    public  function op_plus(other:IRubyObject):IRubyObject{
        var str: RubyString =  other.convertToString();
        
        var result: ByteList =  new ByteList(value.realSize + str.value.realSize);
        result.realSize = value.realSize + str.value.realSize;
        System.arraycopy(value.bytes, value.begin, result.bytes, 0, value.realSize);
        System.arraycopy(str.value.bytes, str.value.begin, result.bytes, value.realSize, str.value.realSize);
      
        var resultStr: RubyString =  newString(getRuntime(), result);
        if (isTaint() || str.isTaint()) resultStr.setTaint(true);
        return resultStr;
    }

    public  function op_mul(other:IRubyObject):IRubyObject{
        var otherInteger: RubyInteger = RubyInteger( other.convertToInteger() );
        var len: Number =  otherInteger.getLongValue();

        if (len < 0) throw getRuntime().newArgumentError("negative argument");

        // we limit to int because ByteBuffer can only allocate int sizes
        if (len > 0 && Integer.MAX_VALUE / len < value.length()) {
            throw getRuntime().newArgumentError("argument too big");
        }
        var newBytes: String =  new String; //(value.length() * (int)len);

        for ( var int:i = 0; i < len; i++) {
            newBytes.append(value);
        }

        var newString: RubyString =  new RubyString(getRuntime(), getMetaClass(), newBytes);
        newString.setTaint(isTaint());
        return newString;
    }

    public  function op_format(arg:IRubyObject):IRubyObject{
        // FIXME: Should we make this work with platform's locale, or continue hardcoding US?
        var s: RubyString =  Sprintf.sprintf(getRuntime(), Locale.US, value, arg);
        s.infectBy(this);
        return s;
    }

    public  function hash():RubyFixnum{
        return getRuntime().newFixnum(value.hashCode());
    }

    public  function hashCode():int{
        return value.hashCode();
    }

    public  function equals(other:Object):Boolean{
        if (this == other) return true;

        if (other instanceof RubyString) {
            var string: RubyString =  RubyString( other);

            if (string.value.equal(value)) return true;
        }

        return false;
    }

    // rb_obj_as_string
    public static function objAsString(context:ThreadContext, obj:IRubyObject):RubyString{
        if (obj instanceof RubyString) return RubyString( obj);

        var str: IRubyObject =  obj.callMethod(context, MethodIndex.TO_S, "to_s");

        if (!(str instanceof RubyString)) return RubyString(obj.anyToString());

        if (obj.isTaint()) str.setTaint(true);

        return RubyString( str);
    }

    // rb_str_cmp
    public  function op_cmp(other:RubyString):int{
        return value.cmp(other.value);
    }

    // rb_to_id
    public  function asJavaString():String{
        // TODO: This used to intern; but it didn't appear to change anything
        // turning that off, and it's unclear if it was needed. Plus, we intern
        // 
        return toString();
    }

    // Create a new String which uses the same Ruby runtime and the same
    // class like this String.
    // This method should be used to satisfy RCR #38.
    public  function newString(s:CharSequence):RubyString{
        return new RubyString(getRuntime(), getType(), s);
    }

    // Create a new String which uses the same Ruby runtime and the same
    // class like this String.
    public  function newString(s:ByteList):RubyString{
        return new RubyString(getRuntime(), getMetaClass(), s);
    }
    */
    // Methods of the String class (rb_str_*):

    // rb_str_new2
    public static function newString(runtime:Ruby, str:String):RubyString{
        return new RubyString(runtime, runtime.getString(), str);
    }
    
    public static function newEmptyString(runtime:Ruby):RubyString{
        return newEmptyString(runtime, runtime.getString());
    }
    /*
    public static function newEmptyString(runtime:Ruby, metaClass:RubyClass):RubyString{
        var empty: RubyString =  new RubyString(runtime, metaClass, ByteList.EMPTY_BYTELIST);
        empty.shareLevel = SHARE_LEVEL_BYTELIST;
        return empty;
    }

    public static function newUnicodeString(runtime:Ruby, str:String):RubyString{
        try {
            return new RubyString(runtime, runtime.getString(), new ByteList(str.getBytes("UTF8"), false));
        } catch (uee:UnsupportedEncodingException) {
            return new RubyString(runtime, runtime.getString(), str);
        }
    }

    public static function newString(runtime:Ruby, clazz:RubyClass, str:CharSequence):RubyString{
        return new RubyString(runtime, clazz, str);
    }

    public static function newString(runtime:Ruby, bytes:Array):RubyString{
        return new RubyString(runtime, runtime.getString(), bytes);
    }

    public static function newString(runtime:Ruby, bytes:ByteList):RubyString{
        return new RubyString(runtime, runtime.getString(), bytes);
    }

    public static function newStringLight(runtime:Ruby, bytes:ByteList):RubyString{
        return new RubyString(runtime, runtime.getString(), bytes, false);
    }

    public static function newStringShared(runtime:Ruby, orig:RubyString):RubyString{
        orig.shareLevel = SHARE_LEVEL_BYTELIST;
        var str: RubyString =  new RubyString(runtime, runtime.getString(), orig.value);
        str.shareLevel = SHARE_LEVEL_BYTELIST;
        return str;
    }       
    
    public static function newStringShared(runtime:Ruby, bytes:ByteList):RubyString{
        return newStringShared(runtime, runtime.getString(), bytes);
    }    

    public static function newStringShared(runtime:Ruby, clazz:RubyClass, bytes:ByteList):RubyString{
        var str: RubyString =  new RubyString(runtime, clazz, bytes);
        str.shareLevel = SHARE_LEVEL_BYTELIST;
        return str;
    }    

    public static function newString(runtime:Ruby, bytes:Array, start:int, length:int):RubyString{
        var bytes2: Array =  new byte[length];
        System.arraycopy(bytes, start, bytes2, 0, length);
        return new RubyString(runtime, runtime.getString(), new ByteList(bytes2, false));
    }

    public  function doClone():IRubyObject{
        return newString(getRuntime(), value.dup());
    }

    public  function cat(str:Array):RubyString{
        modify(value.realSize + str.length);
        System.arraycopy(str, 0, value.bytes, value.begin + value.realSize, str.length);
        value.realSize += str.length;
        return this;
    }

    public  function cat(str:Array, beg:int, len:int):RubyString{
        modify(value.realSize + len);        
        System.arraycopy(str, beg, value.bytes, value.begin + value.realSize, len);
        value.realSize += len;
        return this;
    }

    public  function cat(str:ByteList):RubyString{
        modify(value.realSize + str.realSize);
        System.arraycopy(str.bytes, str.begin, value.bytes, value.begin + value.realSize, str.realSize);
        value.realSize += str.realSize;
        return this;
    }

    public  function cat(ch:byte):RubyString{
        modify(value.realSize + 1);        
        value.bytes[value.begin + value.realSize] = ch;
        value.realSize++;
        return this;
    }
    
    // rb_str_replace_m
    public  function replace(other:IRubyObject):RubyString{
        if (this == other) return this;

        modifyCheck();

        var otherStr: RubyString =   stringValue(other);

        otherStr.shareLevel = shareLevel = SHARE_LEVEL_BYTELIST;

        value = otherStr.value;

        infectBy(other);
        return this;
    }

    public  function reverse():RubyString{
        if (value.length() <= 1) return strDup();

        var buf: ByteList =  new ByteList(value.length()+2);
        buf.realSize = value.length();
        var src: int =  value.length() - 1;
        var dst: int =  0;
        
        while (src >= 0) buf.set(dst++, value.get(src--));

        var rev: RubyString =  new RubyString(getRuntime(), getMetaClass(), buf);
        rev.infectBy(this);
        return rev;
    }

    public  function reverse_bang():RubyString{
        if (value.length() > 1) {
            modify();
            for ( var int:i = 0; i < (value.length() / 2); i++) {
                var b: int =  value.get(i);
                
                value.set(i, value.get(value.length() - i - 1));
                value.set(value.length() - i - 1, b);
            }
        }
        
        return this;
    }

    // rb_str_s_new
    public static function newInstance(recv:IRubyObject, args:Array, block:Block):RubyString{
        var newString: RubyString =  newStringShared(recv.getRuntime(), ByteList.EMPTY_BYTELIST);
        newString.setMetaClass(RubyClass( recv) );
        newString.callInit(args, block);
        return newString;
    }

    public  function initialize(args:Array, unusedBlock:Block):IRubyObject{
        if (args.length == 1) replace(args[0]);

        return this;
    }

    public  function casecmp(other:IRubyObject):IRubyObject{
        var compare: int =  value.caseInsensitiveCmp(stringValue(other).value);
        return RubyFixnum.newFixnum(getRuntime(), compare);
    }

    // rb_str_match
    public  function op_match(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyRegexp) return RubyRegexp(other).op_match(context, this);
        if (other instanceof RubyString) {
            throw getRuntime().newTypeError("type mismatch: String given");
        }
        return other.callMethod(context, "=~", this);
    }

    // rb_str_match2
    public  function op_match2(context:ThreadContext):IRubyObject{
        return RubyRegexp.newRegexp(getRuntime(), value, 0, false).op_match2(context);
    }

    //String#match(pattern)
    // rb_str_match_m
    public  function match(context:ThreadContext, pattern:IRubyObject):IRubyObject{
        return getPattern(pattern, false).callMethod(context, "match", this);
    }

    // rb_str_capitalize
    public  function capitalize():IRubyObject{
        var str: RubyString =  strDup();
        str.capitalize_bang();
        return str;
    }

    // rb_str_capitalize_bang
    public  function capitalize_bang():IRubyObject{        
        if (value.realSize == 0) {
            modifyCheck();
            return getRuntime().getNil();
        }
        
        modify();
        
        var s: int =  value.begin;
        var send: int =  s + value.realSize;
        var buf:String = value.bytes;
        
        
        
        var modify: Boolean =  false;
        
        var c: int =  buf[s] & 0xff;
        if (ASCII.isLower(c)) {
            buf[s] = ASCIIEncoding.asciiToUpper(c);
            modify = true;
        }
        
        while (++s < send) {
            c = (char)(buf[s] & 0xff);
            if (ASCII.isUpper(c)) {
                buf[s] = ASCIIEncoding.asciiToLower(c);
                modify = true;
            }
        }
        
        if (modify) return this;
        return getRuntime().getNil();
    }

    public  function op_ge(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyString) {
            return getRuntime().newBoolean(op_cmp(RubyString( other)) >= 0);
        }

        return RubyComparable.op_ge(context, this, other);
    }

    public  function op_gt(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyString) {
            return getRuntime().newBoolean(op_cmp(RubyString( other)) > 0);
        }

        return RubyComparable.op_gt(context, this, other);
    }

    public  function op_le(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyString) {
            return getRuntime().newBoolean(op_cmp(RubyString( other)) <= 0);
        }

        return RubyComparable.op_le(context, this, other);
    }

    public  function op_lt(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyString) {
            return getRuntime().newBoolean(op_cmp(RubyString( other)) < 0);
        }

        return RubyComparable.op_lt(context, this, other);
    }

    public  function str_eql_p(other:IRubyObject):IRubyObject{
        if (!(other instanceof RubyString)) return getRuntime().getFalse();
        var otherString: RubyString =  RubyString(other);
        return value.equal(otherString.value) ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    // rb_str_upcase
    public  function upcase():RubyString{
        var str: RubyString =  strDup();
        str.upcase_bang();
        return str;
    }

    // rb_str_upcase_bang
    public  function upcase_bang():IRubyObject{
        if (value.realSize == 0) {
            modifyCheck();
            return getRuntime().getNil();
        }

        modify();

        var s: int =  value.begin;
        var send: int =  s + value.realSize;
        var buf:String = value.bytes;

        var modify: Boolean =  false;
        while (s < send) {
            var c: int =  buf[s] & 0xff;
            if (ASCII.isLower(c)) {
                buf[s] = ASCIIEncoding.asciiToUpper(c);
                modify = true;
            }
            s++;
        }

        if (modify) return this;
        return getRuntime().getNil();        
    }

    // rb_str_downcase
    public  function downcase():RubyString{
        var str: RubyString =  strDup();
        str.downcase_bang();
        return str;
    }

    // rb_str_downcase_bang
    public  function downcase_bang():IRubyObject{
        if (value.realSize == 0) {
            modifyCheck();
            return getRuntime().getNil();
        }
        
        modify();
        
        var s: int =  value.begin;
        var send: int =  s + value.realSize;
        var buf:String = value.bytes;
        
        var modify: Boolean =  false;
        while (s < send) {
            var c: int =  buf[s] & 0xff;
            if (ASCII.isUpper(c)) {
                buf[s] = ASCIIEncoding.asciiToLower(c);
                modify = true;
            }
            s++;
        }
        
        if (modify) return this;
        return getRuntime().getNil();
    }

    // rb_str_swapcase
    public  function swapcase():RubyString{
        var str: RubyString =  strDup();
        str.swapcase_bang();
        return str;
    }

    // rb_str_swapcase_bang
    public  function swapcase_bang():IRubyObject{
        if (value.realSize == 0) {
            modifyCheck();
            return getRuntime().getNil();        
        }
        
        modify();
        
        var s: int =  value.begin;
        var send: int =  s + value.realSize;
        var buf:String = value.bytes;
        
        var modify: Boolean =  false;
        while (s < send) {
            var c: int =  buf[s] & 0xff;
            if (ASCII.isUpper(c)) {
                buf[s] = ASCIIEncoding.asciiToLower(c);
                modify = true;
            } else if (ASCII.isLower(c)) {
                buf[s] = ASCIIEncoding.asciiToUpper(c);
                modify = true;
            }
            s++;
        }

        if (modify) return this;
        return getRuntime().getNil();
    }

    // rb_str_dump
    public  function dump():IRubyObject{
        var s: RubyString =  new RubyString(getRuntime(), getMetaClass(), inspectIntoByteList(true));
        s.infectBy(this);
        return s;
    }

    public  function insert(indexArg:IRubyObject, stringArg:IRubyObject):IRubyObject{
        // MRI behavior: first check for ability to convert to String...
        var s: RubyString =  RubyString(stringArg.convertToString());
        var insert: ByteList =  s.value;

        // ... and then the index
        var index: int =   indexArg.convertToInteger().getLongValue();
        if (index < 0) index += value.length() + 1;

        if (index < 0 || index > value.length()) {
            throw getRuntime().newIndexError("index " + index + " out of range");
        }

        modify();

        value.unsafeReplace(index, 0, insert);
        this.infectBy(s);
        return this;
    }

    // rb_str_inspect
    public  function inspect():IRubyObject{
        var s: RubyString =  getRuntime().newString(inspectIntoByteList(false));
        s.infectBy(this);
        return s;
    }
    
    private  function inspectIntoByteList(ignoreKCode:Boolean):ByteList{
        var runtime: Ruby =  getRuntime();
        var enc: Encoding =  runtime.getKCode().getEncoding();
        var length: int =  value.length();
        var sb: ByteList =  new ByteList(length + 2 + length / 100);

        sb.append('\"');

        for ( var int:i = 0; i < length; i++) {
            var c: int =  value.get(i) & 0xFF;
            
            if (!ignoreKCode) {
                var seqLength: int =  enc.length(c);
                
                if (seqLength > 1 && (i + seqLength -1 < length)) {
                    // don't escape multi-byte characters, leave them as bytes
                    sb.append(value, i, seqLength);
                    i += seqLength - 1;
                    continue;
                }
            } 
            
            if (isAlnum(c)) {
                sb.append(c);
            } else if (c == '\"' || c == '\\') {
                sb.append('\\').append(c);
            } else if (c == '#' && isEVStr(i, length)) {
                sb.append('\\').append(c);
            } else if (isPrint(c)) {
                sb.append(c);
            } else if (c == '\n') {
                sb.append('\\').append('n');
            } else if (c == '\r') {
                sb.append('\\').append('r');
            } else if (c == '\t') {
                sb.append('\\').append('t');
            } else if (c == '\f') {
                sb.append('\\').append('f');
            } else if (c == '\u000B') {
                sb.append('\\').append('v');
            } else if (c == '\u0007') {
                sb.append('\\').append('a');
            } else if (c == '\u0008') {
                sb.append('\\').append('b');
            } else if (c == '\u001B') {
                sb.append('\\').append('e');
            } else {
                sb.append(ByteList.plain(Sprintf.sprintf(runtime,"\\%03o",c)));
            }
        }

        sb.append('\"');
        return sb;
    }
    
    private  function isEVStr(i:int, length:int):Boolean{
        if (i+1 >= length) return false;
        var c: int =  value.get(i+1) & 0xFF;
        
        return c == '$' || c == '@' || c == '{';
    }
*/
    // rb_str_length
    public  function length():RubyFixnum{
        return getRuntime().newFixnum(value.length);
    }

    // rb_str_empty
    public  function empty_p():RubyBoolean{
        return isEmpty() ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    public  function isEmpty():Boolean{
        return value.length == 0;
    }

    // rb_str_append
    public  function append(other:IRubyObject):RubyString{
        infectBy(other);
        return cat(stringValue(other).value);
    }

    // rb_str_concat
    public  function concat(other:IRubyObject):RubyString{
        if (other instanceof RubyFixnum) {
            var value: Number =  RubyFixnum( other).getLongValue();
            if (value >= 0 && value < 256) return cat( value);
        }
        return append(other);
    }
/*
    // rb_str_crypt
    public  function crypt(other:IRubyObject):RubyString{
        var salt: String =  stringValue(other).getByteList();
        if (salt.realSize < 2) {
            throw getRuntime().newArgumentError("salt too short(need >=2 bytes)");
        }

        salt = salt.makeShared(0, 2);
        var s: RubyString =  RubyString.newStringShared(getRuntime(), JavaCrypt.crypt(salt, this.getByteList()));
        s.infectBy(this);
        s.infectBy(other);
        return s;
    }

    public static class JavaCrypt {
        private static var ITERATIONS: int =  16;

        private static int con_salt[] = {
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
            0x0A, 0x0B, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
            0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
            0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
            0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22,
            0x23, 0x24, 0x25, 0x20, 0x21, 0x22, 0x23, 0x24,
            0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,
            0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34,
            0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
            0x3D, 0x3E, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00,
        };

        private static boolean shifts2[] = {
            false, false, true, true, true, true, true, true,
            false, true,  true, true, true, true, true, false };

        private static int skb[][] = {
            {
                // for C bits (numbered as per FIPS 46) 1 2 3 4 5 6 
                0x00000000, 0x00000010, 0x20000000, 0x20000010,
                0x00010000, 0x00010010, 0x20010000, 0x20010010,
                0x00000800, 0x00000810, 0x20000800, 0x20000810,
                0x00010800, 0x00010810, 0x20010800, 0x20010810,
                0x00000020, 0x00000030, 0x20000020, 0x20000030,
                0x00010020, 0x00010030, 0x20010020, 0x20010030,
                0x00000820, 0x00000830, 0x20000820, 0x20000830,
                0x00010820, 0x00010830, 0x20010820, 0x20010830,
                0x00080000, 0x00080010, 0x20080000, 0x20080010,
                0x00090000, 0x00090010, 0x20090000, 0x20090010,
                0x00080800, 0x00080810, 0x20080800, 0x20080810,
                0x00090800, 0x00090810, 0x20090800, 0x20090810,
                0x00080020, 0x00080030, 0x20080020, 0x20080030,
                0x00090020, 0x00090030, 0x20090020, 0x20090030,
                0x00080820, 0x00080830, 0x20080820, 0x20080830,
                0x00090820, 0x00090830, 0x20090820, 0x20090830,
            },{
                // for C bits (numbered as per FIPS 46) 7 8 10 11 12 13 
                0x00000000, 0x02000000, 0x00002000, 0x02002000,
                0x00200000, 0x02200000, 0x00202000, 0x02202000,
                0x00000004, 0x02000004, 0x00002004, 0x02002004,
                0x00200004, 0x02200004, 0x00202004, 0x02202004,
                0x00000400, 0x02000400, 0x00002400, 0x02002400,
                0x00200400, 0x02200400, 0x00202400, 0x02202400,
                0x00000404, 0x02000404, 0x00002404, 0x02002404,
                0x00200404, 0x02200404, 0x00202404, 0x02202404,
                0x10000000, 0x12000000, 0x10002000, 0x12002000,
                0x10200000, 0x12200000, 0x10202000, 0x12202000,
                0x10000004, 0x12000004, 0x10002004, 0x12002004,
                0x10200004, 0x12200004, 0x10202004, 0x12202004,
                0x10000400, 0x12000400, 0x10002400, 0x12002400,
                0x10200400, 0x12200400, 0x10202400, 0x12202400,
                0x10000404, 0x12000404, 0x10002404, 0x12002404,
                0x10200404, 0x12200404, 0x10202404, 0x12202404,
            },{
                // for C bits (numbered as per FIPS 46) 14 15 16 17 19 20 
                0x00000000, 0x00000001, 0x00040000, 0x00040001,
                0x01000000, 0x01000001, 0x01040000, 0x01040001,
                0x00000002, 0x00000003, 0x00040002, 0x00040003,
                0x01000002, 0x01000003, 0x01040002, 0x01040003,
                0x00000200, 0x00000201, 0x00040200, 0x00040201,
                0x01000200, 0x01000201, 0x01040200, 0x01040201,
                0x00000202, 0x00000203, 0x00040202, 0x00040203,
                0x01000202, 0x01000203, 0x01040202, 0x01040203,
                0x08000000, 0x08000001, 0x08040000, 0x08040001,
                0x09000000, 0x09000001, 0x09040000, 0x09040001,
                0x08000002, 0x08000003, 0x08040002, 0x08040003,
                0x09000002, 0x09000003, 0x09040002, 0x09040003,
                0x08000200, 0x08000201, 0x08040200, 0x08040201,
                0x09000200, 0x09000201, 0x09040200, 0x09040201,
                0x08000202, 0x08000203, 0x08040202, 0x08040203,
                0x09000202, 0x09000203, 0x09040202, 0x09040203,
            },{
                // for C bits (numbered as per FIPS 46) 21 23 24 26 27 28 
                0x00000000, 0x00100000, 0x00000100, 0x00100100,
                0x00000008, 0x00100008, 0x00000108, 0x00100108,
                0x00001000, 0x00101000, 0x00001100, 0x00101100,
                0x00001008, 0x00101008, 0x00001108, 0x00101108,
                0x04000000, 0x04100000, 0x04000100, 0x04100100,
                0x04000008, 0x04100008, 0x04000108, 0x04100108,
                0x04001000, 0x04101000, 0x04001100, 0x04101100,
                0x04001008, 0x04101008, 0x04001108, 0x04101108,
                0x00020000, 0x00120000, 0x00020100, 0x00120100,
                0x00020008, 0x00120008, 0x00020108, 0x00120108,
                0x00021000, 0x00121000, 0x00021100, 0x00121100,
                0x00021008, 0x00121008, 0x00021108, 0x00121108,
                0x04020000, 0x04120000, 0x04020100, 0x04120100,
                0x04020008, 0x04120008, 0x04020108, 0x04120108,
                0x04021000, 0x04121000, 0x04021100, 0x04121100,
                0x04021008, 0x04121008, 0x04021108, 0x04121108,
            },{
                // for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
                0x00000000, 0x10000000, 0x00010000, 0x10010000,
                0x00000004, 0x10000004, 0x00010004, 0x10010004,
                0x20000000, 0x30000000, 0x20010000, 0x30010000,
                0x20000004, 0x30000004, 0x20010004, 0x30010004,
                0x00100000, 0x10100000, 0x00110000, 0x10110000,
                0x00100004, 0x10100004, 0x00110004, 0x10110004,
                0x20100000, 0x30100000, 0x20110000, 0x30110000,
                0x20100004, 0x30100004, 0x20110004, 0x30110004,
                0x00001000, 0x10001000, 0x00011000, 0x10011000,
                0x00001004, 0x10001004, 0x00011004, 0x10011004,
                0x20001000, 0x30001000, 0x20011000, 0x30011000,
                0x20001004, 0x30001004, 0x20011004, 0x30011004,
                0x00101000, 0x10101000, 0x00111000, 0x10111000,
                0x00101004, 0x10101004, 0x00111004, 0x10111004,
                0x20101000, 0x30101000, 0x20111000, 0x30111000,
                0x20101004, 0x30101004, 0x20111004, 0x30111004,
            },{
                // for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
                0x00000000, 0x08000000, 0x00000008, 0x08000008,
                0x00000400, 0x08000400, 0x00000408, 0x08000408,
                0x00020000, 0x08020000, 0x00020008, 0x08020008,
                0x00020400, 0x08020400, 0x00020408, 0x08020408,
                0x00000001, 0x08000001, 0x00000009, 0x08000009,
                0x00000401, 0x08000401, 0x00000409, 0x08000409,
                0x00020001, 0x08020001, 0x00020009, 0x08020009,
                0x00020401, 0x08020401, 0x00020409, 0x08020409,
                0x02000000, 0x0A000000, 0x02000008, 0x0A000008,
                0x02000400, 0x0A000400, 0x02000408, 0x0A000408,
                0x02020000, 0x0A020000, 0x02020008, 0x0A020008,
                0x02020400, 0x0A020400, 0x02020408, 0x0A020408,
                0x02000001, 0x0A000001, 0x02000009, 0x0A000009,
                0x02000401, 0x0A000401, 0x02000409, 0x0A000409,
                0x02020001, 0x0A020001, 0x02020009, 0x0A020009,
                0x02020401, 0x0A020401, 0x02020409, 0x0A020409,
            },{
                // for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
                0x00000000, 0x00000100, 0x00080000, 0x00080100,
                0x01000000, 0x01000100, 0x01080000, 0x01080100,
                0x00000010, 0x00000110, 0x00080010, 0x00080110,
                0x01000010, 0x01000110, 0x01080010, 0x01080110,
                0x00200000, 0x00200100, 0x00280000, 0x00280100,
                0x01200000, 0x01200100, 0x01280000, 0x01280100,
                0x00200010, 0x00200110, 0x00280010, 0x00280110,
                0x01200010, 0x01200110, 0x01280010, 0x01280110,
                0x00000200, 0x00000300, 0x00080200, 0x00080300,
                0x01000200, 0x01000300, 0x01080200, 0x01080300,
                0x00000210, 0x00000310, 0x00080210, 0x00080310,
                0x01000210, 0x01000310, 0x01080210, 0x01080310,
                0x00200200, 0x00200300, 0x00280200, 0x00280300,
                0x01200200, 0x01200300, 0x01280200, 0x01280300,
                0x00200210, 0x00200310, 0x00280210, 0x00280310,
                0x01200210, 0x01200310, 0x01280210, 0x01280310,
            },{
                // for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
                0x00000000, 0x04000000, 0x00040000, 0x04040000,
                0x00000002, 0x04000002, 0x00040002, 0x04040002,
                0x00002000, 0x04002000, 0x00042000, 0x04042000,
                0x00002002, 0x04002002, 0x00042002, 0x04042002,
                0x00000020, 0x04000020, 0x00040020, 0x04040020,
                0x00000022, 0x04000022, 0x00040022, 0x04040022,
                0x00002020, 0x04002020, 0x00042020, 0x04042020,
                0x00002022, 0x04002022, 0x00042022, 0x04042022,
                0x00000800, 0x04000800, 0x00040800, 0x04040800,
                0x00000802, 0x04000802, 0x00040802, 0x04040802,
                0x00002800, 0x04002800, 0x00042800, 0x04042800,
                0x00002802, 0x04002802, 0x00042802, 0x04042802,
                0x00000820, 0x04000820, 0x00040820, 0x04040820,
                0x00000822, 0x04000822, 0x00040822, 0x04040822,
                0x00002820, 0x04002820, 0x00042820, 0x04042820,
                0x00002822, 0x04002822, 0x00042822, 0x04042822,
            }
        };

        private static int SPtrans[][] = {
            {
                // nibble 0 
                0x00820200, 0x00020000, 0x80800000, 0x80820200,
                0x00800000, 0x80020200, 0x80020000, 0x80800000,
                0x80020200, 0x00820200, 0x00820000, 0x80000200,
                0x80800200, 0x00800000, 0x00000000, 0x80020000,
                0x00020000, 0x80000000, 0x00800200, 0x00020200,
                0x80820200, 0x00820000, 0x80000200, 0x00800200,
                0x80000000, 0x00000200, 0x00020200, 0x80820000,
                0x00000200, 0x80800200, 0x80820000, 0x00000000,
                0x00000000, 0x80820200, 0x00800200, 0x80020000,
                0x00820200, 0x00020000, 0x80000200, 0x00800200,
                0x80820000, 0x00000200, 0x00020200, 0x80800000,
                0x80020200, 0x80000000, 0x80800000, 0x00820000,
                0x80820200, 0x00020200, 0x00820000, 0x80800200,
                0x00800000, 0x80000200, 0x80020000, 0x00000000,
                0x00020000, 0x00800000, 0x80800200, 0x00820200,
                0x80000000, 0x80820000, 0x00000200, 0x80020200,
            },{
                // nibble 1
                0x10042004, 0x00000000, 0x00042000, 0x10040000,
                0x10000004, 0x00002004, 0x10002000, 0x00042000,
                0x00002000, 0x10040004, 0x00000004, 0x10002000,
                0x00040004, 0x10042000, 0x10040000, 0x00000004,
                0x00040000, 0x10002004, 0x10040004, 0x00002000,
                0x00042004, 0x10000000, 0x00000000, 0x00040004,
                0x10002004, 0x00042004, 0x10042000, 0x10000004,
                0x10000000, 0x00040000, 0x00002004, 0x10042004,
                0x00040004, 0x10042000, 0x10002000, 0x00042004,
                0x10042004, 0x00040004, 0x10000004, 0x00000000,
                0x10000000, 0x00002004, 0x00040000, 0x10040004,
                0x00002000, 0x10000000, 0x00042004, 0x10002004,
                0x10042000, 0x00002000, 0x00000000, 0x10000004,
                0x00000004, 0x10042004, 0x00042000, 0x10040000,
                0x10040004, 0x00040000, 0x00002004, 0x10002000,
                0x10002004, 0x00000004, 0x10040000, 0x00042000,
            },{
                // nibble 2
                0x41000000, 0x01010040, 0x00000040, 0x41000040,
                0x40010000, 0x01000000, 0x41000040, 0x00010040,
                0x01000040, 0x00010000, 0x01010000, 0x40000000,
                0x41010040, 0x40000040, 0x40000000, 0x41010000,
                0x00000000, 0x40010000, 0x01010040, 0x00000040,
                0x40000040, 0x41010040, 0x00010000, 0x41000000,
                0x41010000, 0x01000040, 0x40010040, 0x01010000,
                0x00010040, 0x00000000, 0x01000000, 0x40010040,
                0x01010040, 0x00000040, 0x40000000, 0x00010000,
                0x40000040, 0x40010000, 0x01010000, 0x41000040,
                0x00000000, 0x01010040, 0x00010040, 0x41010000,
                0x40010000, 0x01000000, 0x41010040, 0x40000000,
                0x40010040, 0x41000000, 0x01000000, 0x41010040,
                0x00010000, 0x01000040, 0x41000040, 0x00010040,
                0x01000040, 0x00000000, 0x41010000, 0x40000040,
                0x41000000, 0x40010040, 0x00000040, 0x01010000,
            },{
                // nibble 3
                0x00100402, 0x04000400, 0x00000002, 0x04100402,
                0x00000000, 0x04100000, 0x04000402, 0x00100002,
                0x04100400, 0x04000002, 0x04000000, 0x00000402,
                0x04000002, 0x00100402, 0x00100000, 0x04000000,
                0x04100002, 0x00100400, 0x00000400, 0x00000002,
                0x00100400, 0x04000402, 0x04100000, 0x00000400,
                0x00000402, 0x00000000, 0x00100002, 0x04100400,
                0x04000400, 0x04100002, 0x04100402, 0x00100000,
                0x04100002, 0x00000402, 0x00100000, 0x04000002,
                0x00100400, 0x04000400, 0x00000002, 0x04100000,
                0x04000402, 0x00000000, 0x00000400, 0x00100002,
                0x00000000, 0x04100002, 0x04100400, 0x00000400,
                0x04000000, 0x04100402, 0x00100402, 0x00100000,
                0x04100402, 0x00000002, 0x04000400, 0x00100402,
                0x00100002, 0x00100400, 0x04100000, 0x04000402,
                0x00000402, 0x04000000, 0x04000002, 0x04100400,
            },{
                // nibble 4
                0x02000000, 0x00004000, 0x00000100, 0x02004108,
                0x02004008, 0x02000100, 0x00004108, 0x02004000,
                0x00004000, 0x00000008, 0x02000008, 0x00004100,
                0x02000108, 0x02004008, 0x02004100, 0x00000000,
                0x00004100, 0x02000000, 0x00004008, 0x00000108,
                0x02000100, 0x00004108, 0x00000000, 0x02000008,
                0x00000008, 0x02000108, 0x02004108, 0x00004008,
                0x02004000, 0x00000100, 0x00000108, 0x02004100,
                0x02004100, 0x02000108, 0x00004008, 0x02004000,
                0x00004000, 0x00000008, 0x02000008, 0x02000100,
                0x02000000, 0x00004100, 0x02004108, 0x00000000,
                0x00004108, 0x02000000, 0x00000100, 0x00004008,
                0x02000108, 0x00000100, 0x00000000, 0x02004108,
                0x02004008, 0x02004100, 0x00000108, 0x00004000,
                0x00004100, 0x02004008, 0x02000100, 0x00000108,
                0x00000008, 0x00004108, 0x02004000, 0x02000008,
            },{
                // nibble 5
                0x20000010, 0x00080010, 0x00000000, 0x20080800,
                0x00080010, 0x00000800, 0x20000810, 0x00080000,
                0x00000810, 0x20080810, 0x00080800, 0x20000000,
                0x20000800, 0x20000010, 0x20080000, 0x00080810,
                0x00080000, 0x20000810, 0x20080010, 0x00000000,
                0x00000800, 0x00000010, 0x20080800, 0x20080010,
                0x20080810, 0x20080000, 0x20000000, 0x00000810,
                0x00000010, 0x00080800, 0x00080810, 0x20000800,
                0x00000810, 0x20000000, 0x20000800, 0x00080810,
                0x20080800, 0x00080010, 0x00000000, 0x20000800,
                0x20000000, 0x00000800, 0x20080010, 0x00080000,
                0x00080010, 0x20080810, 0x00080800, 0x00000010,
                0x20080810, 0x00080800, 0x00080000, 0x20000810,
                0x20000010, 0x20080000, 0x00080810, 0x00000000,
                0x00000800, 0x20000010, 0x20000810, 0x20080800,
                0x20080000, 0x00000810, 0x00000010, 0x20080010,
            },{
                // nibble 6
                0x00001000, 0x00000080, 0x00400080, 0x00400001,
                0x00401081, 0x00001001, 0x00001080, 0x00000000,
                0x00400000, 0x00400081, 0x00000081, 0x00401000,
                0x00000001, 0x00401080, 0x00401000, 0x00000081,
                0x00400081, 0x00001000, 0x00001001, 0x00401081,
                0x00000000, 0x00400080, 0x00400001, 0x00001080,
                0x00401001, 0x00001081, 0x00401080, 0x00000001,
                0x00001081, 0x00401001, 0x00000080, 0x00400000,
                0x00001081, 0x00401000, 0x00401001, 0x00000081,
                0x00001000, 0x00000080, 0x00400000, 0x00401001,
                0x00400081, 0x00001081, 0x00001080, 0x00000000,
                0x00000080, 0x00400001, 0x00000001, 0x00400080,
                0x00000000, 0x00400081, 0x00400080, 0x00001080,
                0x00000081, 0x00001000, 0x00401081, 0x00400000,
                0x00401080, 0x00000001, 0x00001001, 0x00401081,
                0x00400001, 0x00401080, 0x00401000, 0x00001001,
            },{
                // nibble 7
                0x08200020, 0x08208000, 0x00008020, 0x00000000,
                0x08008000, 0x00200020, 0x08200000, 0x08208020,
                0x00000020, 0x08000000, 0x00208000, 0x00008020,
                0x00208020, 0x08008020, 0x08000020, 0x08200000,
                0x00008000, 0x00208020, 0x00200020, 0x08008000,
                0x08208020, 0x08000020, 0x00000000, 0x00208000,
                0x08000000, 0x00200000, 0x08008020, 0x08200020,
                0x00200000, 0x00008000, 0x08208000, 0x00000020,
                0x00200000, 0x00008000, 0x08000020, 0x08208020,
                0x00008020, 0x08000000, 0x00000000, 0x00208000,
                0x08200020, 0x08008020, 0x08008000, 0x00200020,
                0x08208000, 0x00000020, 0x00200020, 0x08008000,
                0x08208020, 0x00200000, 0x08200000, 0x08000020,
                0x00208000, 0x00008020, 0x08008020, 0x08200000,
                0x00000020, 0x08208000, 0x00208020, 0x00000000,
                0x08000000, 0x08200020, 0x00008000, 0x00208020
            }
        };

        private static int cov_2char[] = {
            0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
            0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44,
            0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C,
            0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
            0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62,
            0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
            0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72,
            0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
        };

        private static function byteToUnsigned(b:byte):int{
            return b & 0xFF;
        }

        private static int fourBytesToInt(byte b[], int offset) {
            var value:int;
            value  =  byteToUnsigned(b[offset++]);
            value |= (byteToUnsigned(b[offset++]) <<  8);
            value |= (byteToUnsigned(b[offset++]) << 16);
            value |= (byteToUnsigned(b[offset++]) << 24);
            return(value);
        }

        private static void intToFourBytes(int iValue, byte b[], int offset) {
            b[offset++] = ((iValue)        & 0xff);
            b[offset++] = ((iValue >>> 8 ) & 0xff);
            b[offset++] = ((iValue >>> 16) & 0xff);
            b[offset++] = ((iValue >>> 24) & 0xff);
        }

        private static void PERM_OP(int a, int b, int n, int m, int results[]) {
            var t:int;

            t = ((a >>> n) ^ b) & m;
            a ^= t << n;
            b ^= t;

            results[0] = a;
            results[1] = b;
        }

        private static function HPERM_OP(a:int, n:int, m:int):int{
            var t:int;

            t = ((a << (16 - n)) ^ a) & m;
            a = a ^ t ^ (t >>> (16 - n));

            return a;
        }

        private static int [] des_set_key(byte key[]) {
            int schedule[] = new int[ITERATIONS * 2];

            var c: int =  fourBytesToInt(key, 0);
            var d: int =  fourBytesToInt(key, 4);

            int results[] = new int[2];

            PERM_OP(d, c, 4, 0x0f0f0f0f, results);
            d = results[0]; c = results[1];

            c = HPERM_OP(c, -2, 0xcccc0000);
            d = HPERM_OP(d, -2, 0xcccc0000);

            PERM_OP(d, c, 1, 0x55555555, results);
            d = results[0]; c = results[1];

            PERM_OP(c, d, 8, 0x00ff00ff, results);
            c = results[0]; d = results[1];

            PERM_OP(d, c, 1, 0x55555555, results);
            d = results[0]; c = results[1];

            d = (((d & 0x000000ff) <<  16) |  (d & 0x0000ff00)     |
                 ((d & 0x00ff0000) >>> 16) | ((c & 0xf0000000) >>> 4));
            c &= 0x0fffffff;

            int s, t;
            var j: int =  0;

            for ( var int:i = 0; i < ITERATIONS; i ++) {
                if(shifts2[i]) {
                    c = (c >>> 2) | (c << 26);
                    d = (d >>> 2) | (d << 26);
                } else {
                    c = (c >>> 1) | (c << 27);
                    d = (d >>> 1) | (d << 27);
                }

                c &= 0x0fffffff;
                d &= 0x0fffffff;

                s = skb[0][ (c       ) & 0x3f                       ]|
                    skb[1][((c >>>  6) & 0x03) | ((c >>>  7) & 0x3c)]|
                    skb[2][((c >>> 13) & 0x0f) | ((c >>> 14) & 0x30)]|
                    skb[3][((c >>> 20) & 0x01) | ((c >>> 21) & 0x06) |
                           ((c >>> 22) & 0x38)];

                t = skb[4][ (d     )  & 0x3f                       ]|
                    skb[5][((d >>> 7) & 0x03) | ((d >>>  8) & 0x3c)]|
                    skb[6][ (d >>>15) & 0x3f                       ]|
                    skb[7][((d >>>21) & 0x0f) | ((d >>> 22) & 0x30)];

                schedule[j++] = ((t <<  16) | (s & 0x0000ffff)) & 0xffffffff;
                s             = ((s >>> 16) | (t & 0xffff0000));

                s             = (s << 4) | (s >>> 28);
                schedule[j++] = s & 0xffffffff;
            }
            return(schedule);
        }

        private static int D_ENCRYPT(int L, int R, int S, int E0, int E1, int s[]) {
            int t, u, v;

            v = R ^ (R >>> 16);
            u = v & E0;
            v = v & E1;
            u = (u ^ (u << 16)) ^ R ^ s[S];
            t = (v ^ (v << 16)) ^ R ^ s[S + 1];
            t = (t >>> 4) | (t << 28);

            L ^= SPtrans[1][(t       ) & 0x3f] |
                SPtrans[3][(t >>>  8) & 0x3f] |
                SPtrans[5][(t >>> 16) & 0x3f] |
                SPtrans[7][(t >>> 24) & 0x3f] |
                SPtrans[0][(u       ) & 0x3f] |
                SPtrans[2][(u >>>  8) & 0x3f] |
                SPtrans[4][(u >>> 16) & 0x3f] |
                SPtrans[6][(u >>> 24) & 0x3f];

            return(L);
        }

        private static int [] body(int schedule[], int Eswap0, int Eswap1) {
            var left: int =  0;
            var right: int =  0;
            var t: int =  0;

            for ( var int:j = 0; j < 25; j ++) {
                for ( var int:i = 0; i < ITERATIONS * 2; i += 4) {
                    left  = D_ENCRYPT(left,  right, i,     Eswap0, Eswap1, schedule);
                    right = D_ENCRYPT(right, left,  i + 2, Eswap0, Eswap1, schedule);
                }
                t     = left;
                left  = right;
                right = t;
            }

            t = right;

            right = (left >>> 1) | (left << 31);
            left  = (t    >>> 1) | (t    << 31);

            left  &= 0xffffffff;
            right &= 0xffffffff;

            int results[] = new int[2];

            PERM_OP(right, left, 1, 0x55555555, results);
            right = results[0]; left = results[1];

            PERM_OP(left, right, 8, 0x00ff00ff, results);
            left = results[0]; right = results[1];

            PERM_OP(right, left, 2, 0x33333333, results);
            right = results[0]; left = results[1];

            PERM_OP(left, right, 16, 0x0000ffff, results);
            left = results[0]; right = results[1];

            PERM_OP(right, left, 4, 0x0f0f0f0f, results);
            right = results[0]; left = results[1];

            int out[] = new int[2];

            out[0] = left; out[1] = right;

            return(out);
        }

        public static function crypt(salt:ByteList, original:ByteList):ByteList{
            var buffer: ByteList =  new ByteList(new byte[]{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},false);

            var charZero: byte =  salt.bytes[salt.begin];
            var charOne: byte =  salt.bytes[salt.begin+1];

            buffer.set(0,charZero);
            buffer.set(1,charOne);

            var Eswap0: int =  con_salt[(charZero&0x7F)];
            var Eswap1: int =  con_salt[(charOne&0x7F)] << 4;

            byte key[] = new byte[8];

            for ( var int:i = 0; i < key.length; i ++) {
                key[i] = 0;
            }

            for ( var int:i = 0; i < key.length && i < original.length(); i ++) {
                var iChar: int =  (original.bytes[original.begin+i]&0xFF);

                key[i] = (iChar << 1);
            }

            int schedule[] = des_set_key(key);
            int out[]      = body(schedule, Eswap0, Eswap1);

            byte b[] = new byte[9];

            intToFourBytes(out[0], b, 0);
            intToFourBytes(out[1], b, 4);
            b[8] = 0;

            for ( var int:i = 2, y = 0, u = 0x80; i < 13; i ++) {
                for ( var int:j = 0, c = 0; j < 6; j ++) {
                    c <<= 1;

                    if((b[y] & u) != 0)
                        c |= 1;

                    u >>>= 1;

                    if(u == 0) {
                        y++;
                        u = 0x80;
                    }
                    buffer.set(i, cov_2char[c]);
                }
            }
            return buffer;
        }
    }

    // RubyString aka rb_string_value
    public static function stringValue(object:IRubyObject):RubyString{
        return (RubyString) (object instanceof RubyString ? object :
            object.convertToString());
    }

    //* rb_str_sub
    public  function sub(context:ThreadContext, args:Array, block:Block):IRubyObject{
        var str: RubyString =  strDup();
        str.sub_bang(context, args, block);
        return str;
    }

    //* rb_str_sub_bang
    public  function sub_bang(context:ThreadContext, args:Array, block:Block):IRubyObject{
        
        var repl: RubyString =  null;
        var iter:Boolean;
        var tainted: Boolean =  false;
        
        if(args.length == 1 && block.isGiven()) {
            iter = true;
            tainted = false;
        } else if(args.length == 2) {
            repl = args[1].convertToString();
            iter = false;
            if (repl.isTaint()) tainted = true;
        } else {
            throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 2)");
        }

        var rubyRegex: RubyRegexp =  getPattern(args[0], true);
        var regex: Regex =  rubyRegex.getPattern();

        var range: int =  value.begin + value.realSize;
        var matcher: Matcher =  regex.matcher(value.bytes, value.begin, range);
        
        var frame: Frame =  context.getPreviousFrame();
        if (matcher.search(value.begin, range, Option.NONE) >= 0) {
            if (iter) {                
                var bytes:String = value.bytes;
                var size: int =  value.realSize;
                var match: RubyMatchData =  rubyRegex.updateBackRef(this, frame, matcher);
                match.use();
                if (regex.numberOfCaptures() == 0) {
                    repl = objAsString(context, block.yield(context, substr(matcher.getBegin(), matcher.getEnd() - matcher.getBegin())));
                } else {
                    var region: Region =  matcher.getRegion();
                    repl = objAsString(context, block.yield(context, substr(region.beg[0], region.end[0] - region.beg[0])));
                }
                modifyCheck(bytes, size);
                frozenCheck();
                frame.setBackRef(match);
            } else {
                repl = rubyRegex.regsub(repl, this, matcher);
                rubyRegex.updateBackRef(this, frame, matcher);
            }

            var beg, plen:int;
            if (regex.numberOfCaptures() == 0) {
                beg = matcher.getBegin();
                plen = matcher.getEnd() - beg;
            } else {
                var region: Region =  matcher.getRegion();
                beg = region.beg[0];
                plen = region.end[0] - beg;
            }

            var replValue: ByteList =  repl.value;
            if (replValue.realSize > plen) {
                modify(value.realSize + replValue.realSize - plen);
            } else {
                modify();
            }
            if (repl.isTaint()) tainted = true;            

            if (replValue.realSize != plen) {
                var src: int =  value.begin + beg + plen;
                var dst: int =  value.begin + beg + replValue.realSize;
                var length: int =  value.realSize - beg - plen;
                System.arraycopy(value.bytes, src, value.bytes, dst, length);
            }
            System.arraycopy(replValue.bytes, replValue.begin, value.bytes, value.begin + beg, replValue.realSize);
            value.realSize += replValue.realSize - plen;
            if (tainted) setTaint(true);
            return this;            
        } else {
            frame.setBackRef(getRuntime().getNil());
            return getRuntime().getNil();
        }
    }
    
    //* rb_str_gsub
    public  function gsub(context:ThreadContext, args:Array, block:Block):IRubyObject{
        return gsub(context, args, block, false);
    }

    //* rb_str_gsub_bang
    public  function gsub_bang(context:ThreadContext, args:Array, block:Block):IRubyObject{
        return gsub(context, args, block, true);
    }

    private  function gsub(context:ThreadContext, args:Array, block:Block, bang:Boolean):IRubyObject{
        var repl:IRubyObject;
        var iter:Boolean;
        var tainted: Boolean =  false;
        
        if (args.length == 1 && block.isGiven()) {
            iter = true;
            repl = null;
        } else if (args.length == 2) {
            iter = false;
            repl = args[1].convertToString();
            if (repl.isTaint()) tainted = true; 
        } else {
            throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + "for 2)");
        }
        
        var rubyRegex: RubyRegexp =  getPattern(args[0], true);
        var regex: Regex =  rubyRegex.getPattern();

        var begin: int =  value.begin;
        var range: int =  begin + value.realSize;
        var matcher: Matcher =  regex.matcher(value.bytes, begin, range);
        
        var beg: int =  matcher.search(begin, range, Option.NONE);

        var frame: Frame =  context.getPreviousFrame();
        
        if (beg < 0) {
            frame.setBackRef(getRuntime().getNil());
            return bang ? getRuntime().getNil() : strDup(); // bang: true, no match, no substitution
        }
        
        var blen: int =  value.realSize + 30; // len + margin 
        var dest: ByteList =  new ByteList(blen);
        dest.realSize = blen;
        var buf: int =  0, bp = 0;
        var cp: int =  value.begin;
        
        var offset: int =  0;
        var val:RubyString;

        var match: RubyMatchData =  null;
        while (beg >= 0) {
            var begz, endz:int;
            if (iter) {
                var bytes:String = value.bytes;
                var size: int =  value.realSize;
                match = rubyRegex.updateBackRef(this, frame, matcher);
                match.use();
                if (regex.numberOfCaptures() == 0) {
                    begz = matcher.getBegin();
                    endz = matcher.getEnd();
                    val = objAsString(context, block.yield(context, substr(begz, endz - begz)));
                } else {
                    var region: Region =  matcher.getRegion();
                    begz = region.beg[0];
                    endz = region.end[0];
                    val = objAsString(context, block.yield(context, substr(begz, endz - begz)));
 
                }
                modifyCheck(bytes, size);
                if (bang) frozenCheck();
            } else {
                val = rubyRegex.regsub(RubyString(repl), this, matcher);
                if (regex.numberOfCaptures() == 0) {
                    begz = matcher.getBegin();
                    endz = matcher.getEnd();
                } else {
                    var region: Region =  matcher.getRegion();
                    begz = region.beg[0];
                    endz = region.end[0];
                }
            }
            
            if (val.isTaint()) tainted = true;
            var vbuf: ByteList =  val.value;
            var len: int =  (bp - buf) + (beg - offset) + vbuf.realSize + 3;
            if (blen < len) {
                while (blen < len) blen <<= 1;
                len = bp - buf;
                dest.realloc(blen);
                dest.realSize = blen;
                bp = buf + len;
            }
            len = beg - offset; // copy pre-match substr
            System.arraycopy(value.bytes, cp, dest.bytes, bp, len);
            bp += len;
            System.arraycopy(vbuf.bytes, vbuf.begin, dest.bytes, bp, vbuf.realSize);
            bp += vbuf.realSize;
            offset = endz;
            
            if (begz == endz) {
                if (value.realSize <= endz) break;
                len = regex.getEncoding().length(value.bytes[begin + endz]);
                System.arraycopy(value.bytes, begin + endz, dest.bytes, bp, len);
                bp += len;
                offset = endz + len;
            }
            cp = begin + offset;
            if (offset > value.realSize) break;
            beg = matcher.search(cp, range, Option.NONE); 
        }
        
        if (value.realSize > offset) {
            var len: int =  bp - buf;
            if (blen - len < value.realSize - offset) {
                blen = len + value.realSize - offset;
                dest.realloc(blen);
                bp = buf + len;
            }
            System.arraycopy(value.bytes, cp, dest.bytes, bp, value.realSize - offset);
            bp += value.realSize - offset;
        }        
        
        if (match != null) {
            frame.setBackRef(match);
        } else {
            rubyRegex.updateBackRef(this, frame, matcher);
        }

        dest.realSize = bp - buf;
        if (bang) {
            view(dest);
            if (tainted) setTaint(true);
            return this;
        } else {
            var destStr: RubyString =  new RubyString(getRuntime(), getMetaClass(), dest);
            destStr.infectBy(this);
            if (tainted) destStr.setTaint(true);
            return destStr;
        }
    }

    //* rb_str_index_m
    public  function index(context:ThreadContext, args:Array):IRubyObject{
        var pos: int =  args.length == 2 ? RubyNumeric.num2int(args[1]) : 0;  
        var sub: IRubyObject =  args[0];
        
        if (pos < 0) {
            pos += value.realSize;
            if (pos < 0) {
                if (sub instanceof RubyRegexp) { 
                    context.getPreviousFrame().setBackRef(getRuntime().getNil());
                }
                return getRuntime().getNil();
            }
        }

        if (sub instanceof RubyRegexp) {
            var regSub: RubyRegexp = RubyRegexp(sub);
            
            pos = regSub.adjustStartPos(this, pos, false);
            pos = regSub.search(context, this, pos, false);
        } else if (sub instanceof RubyFixnum) {
            var c_int: int =  RubyNumeric.fix2int(sub);
            if (c_int < 0x00 || c_int > 0xFF) {
                // out of byte range
                // there will be no match for sure
                return getRuntime().getNil();
            }
            var c: byte =  c_int;
            var bytes:String = value.bytes;
            var end: int =  value.begin + value.realSize;

            pos += value.begin; 
            for (;pos<end; pos++) { 
                if (bytes[pos] == c) return RubyFixnum.newFixnum(getRuntime(), pos - value.begin);
            }
            return getRuntime().getNil();
        } else if (sub instanceof RubyString) {
            pos = strIndex(RubyString(sub), pos);
        } else {
            var tmp: IRubyObject =  sub.checkStringType();
            
            if (tmp.isNil()) throw getRuntime().newTypeError("type mismatch: " + sub.getMetaClass().getName() + " given");
            pos = strIndex(RubyString(tmp), pos);
        }
        
        return pos == -1  ? getRuntime().getNil() : RubyFixnum.newFixnum(getRuntime(), pos);        
    }
    
    private  function strIndex(sub:RubyString, offset:int):int{
        if (offset < 0) {
            offset += value.realSize;
            if (offset < 0) return -1;
        }
        
        if (value.realSize - offset < sub.value.realSize) return -1;
        if (sub.value.realSize == 0) return offset;
        return value.indexOf(sub.value, offset);
    }

    //* rb_str_rindex_m
    public  function rindex(context:ThreadContext, args:Array):IRubyObject{
        var pos:int;
        var sub:IRubyObject;
        
        if (args.length == 2) {  
            sub = args[0];
            pos = RubyNumeric.num2int(args[1]);

            if (pos < 0) {
                pos += value.realSize;
                if (pos < 0) {
                    if (sub instanceof RubyRegexp) { 
                        context.getPreviousFrame().setBackRef(getRuntime().getNil());
                    }
                    return getRuntime().getNil();
                }
            }            
            if (pos > value.realSize) pos = value.realSize;
        } else {
            sub = args[0];
            pos = value.realSize;
        }
        
        if (sub instanceof RubyRegexp) {
            var regSub: RubyRegexp = RubyRegexp(sub);
            if(regSub.length() > 0) {
                pos = regSub.adjustStartPos(this, pos, true);
                pos = regSub.search(context, this, pos, true);
            }
            if (pos >= 0) return RubyFixnum.newFixnum(getRuntime(), pos);
        } else if (sub instanceof RubyString) {
            pos = strRindex(RubyString(sub), pos);
            if (pos >= 0) return RubyFixnum.newFixnum(getRuntime(), pos);
        } else if (sub instanceof RubyFixnum) {
            var c_int: int =  RubyNumeric.fix2int(sub);
            if (c_int < 0x00 || c_int > 0xFF) {
                // out of byte range
                // there will be no match for sure
                return getRuntime().getNil();
            }
            var c: byte =  c_int;

            var bytes:String = value.bytes;
            var pbeg: int =  value.begin;
            var p: int =  pbeg + pos;
            
            if (pos == value.realSize) {
                if (pos == 0) return getRuntime().getNil();
                --p;
            }
            while (pbeg <= p) {
                if (bytes[p] == c) return RubyFixnum.newFixnum(getRuntime(), p - value.begin);
                p--;
            }
            return getRuntime().getNil();
        } else {
            throw getRuntime().newTypeError("type mismatch: " + sub.getMetaClass().getName() + " given");
        }
        
        return getRuntime().getNil();
    }

    private  function strRindex(sub:RubyString, pos:int):int{
        var subLength: int =  sub.value.realSize;
        
        // substring longer than string 
        if (value.realSize < subLength) return -1;
        if (value.realSize - pos < subLength) pos = value.realSize - subLength;

        return value.lastIndexOf(sub.value, pos);
    }
    
    // rb_str_substr 
    public  function substr(beg:int, len:int):IRubyObject{
        var length: int =  value.length();
        if (len < 0 || beg > length) return getRuntime().getNil();

        if (beg < 0) {
            beg += length;
            if (beg < 0) return getRuntime().getNil();
        }
        
        var end: int =  Math.min(length, beg + len);
        return makeShared(beg, end - beg);
    }

    // rb_str_replace
    public  function replace(beg:int, len:int, replaceWith:RubyString):IRubyObject{
        if (beg + len >= value.length()) len = value.length() - beg;

        modify();
        value.unsafeReplace(beg,len,replaceWith.value);

        return infectBy(replaceWith);
    }

    //* rb_str_aref, rb_str_aref_m
    public  function op_aref(context:ThreadContext, args:Array):IRubyObject{
        if (args.length == 2) {
            if (args[0] instanceof RubyRegexp) {
                if((RubyRegexp(args[0])).search(context, this,0,false) >= 0) {
                    return RubyRegexp.nth_match(RubyNumeric.fix2int(args[1]), context.getCurrentFrame().getBackRef());
                }
                return getRuntime().getNil();
            }
            return substr(RubyNumeric.fix2int(args[0]), RubyNumeric.fix2int(args[1]));
        }

        if (args[0] instanceof RubyRegexp) {
            if((RubyRegexp(args[0])).search(context, this,0,false) >= 0) {
                return RubyRegexp.nth_match(0, context.getCurrentFrame().getBackRef());
            }
            return getRuntime().getNil();
        } else if (args[0] instanceof RubyString) {
            return value.indexOf(stringValue(args[0]).value) != -1 ?
                args[0] : getRuntime().getNil();
        } else if (args[0] instanceof RubyRange) {
            var begLen: Array =  RubyRange(args[0]).begLen(value.length(), 0);
            return begLen == null ? getRuntime().getNil() :
                substr( begLen[0],  begLen[1]);
        }
        var idx: int =   args[0].convertToInteger().getLongValue();
        
        if (idx < 0) idx += value.length();
        if (idx < 0 || idx >= value.length()) return getRuntime().getNil();

        return getRuntime().newFixnum(value.get(idx) & 0xFF);
    }

    //rb_str_subpat_set
    private  function subpatSet(context:ThreadContext, regexp:RubyRegexp, nth:int, repl:IRubyObject):void{
        var match:RubyMatchData;
        var start, end, len:int;        
        if (regexp.search(context, this, 0, false) < 0) throw getRuntime().newIndexError("regexp not matched");

        match = RubyMatchData(context).getCurrentFrame().getBackRef();

        if (match.regs == null) {
            if (nth >= 1) throw getRuntime().newIndexError("index " + nth + " out of regexp");
            if (nth < 0) {
                if(-nth >= 1) throw getRuntime().newIndexError("index " + nth + " out of regexp");
                nth += 1;
            }
            start = match.begin;
            if(start == -1) throw getRuntime().newIndexError("regexp group " + nth + " not matched");
            end = match.end;
        } else {
            if(nth >= match.regs.numRegs) throw getRuntime().newIndexError("index " + nth + " out of regexp");
            if(nth < 0) {
                if(-nth >= match.regs.numRegs) throw getRuntime().newIndexError("index " + nth + " out of regexp");
                nth += match.regs.numRegs;
            }
            start = match.regs.beg[nth];
            if(start == -1) throw getRuntime().newIndexError("regexp group " + nth + " not matched");
            end = match.regs.end[nth];
        }
        
        len = end - start;
        replace(start, len, stringValue(repl));
    }

    //* rb_str_aset, rb_str_aset_m
    public  function op_aset(context:ThreadContext, args:Array):IRubyObject{
        var strLen: int =  value.length();
        if (args.length == 3) {
            if (args[0] instanceof RubyRegexp) {
                var repl: RubyString =  stringValue(args[2]);
                var nth: int =  RubyNumeric.fix2int(args[1]);
                subpatSet(context, RubyRegexp(args[0]), nth, repl);
                return repl;
            }
            var repl: RubyString =  stringValue(args[2]);
            var beg: int =  RubyNumeric.fix2int(args[0]);
            var len: int =  RubyNumeric.fix2int(args[1]);
            if (len < 0) throw getRuntime().newIndexError("negative length");
            if (beg < 0) beg += strLen;

            if (beg < 0 || (beg > 0 && beg > strLen)) {
                throw getRuntime().newIndexError("string index out of bounds");
            }
            if (beg + len > strLen) len = strLen - beg;

            replace(beg, len, repl);
            return repl;
        }
        if (args[0] instanceof RubyFixnum || args[0].respondsTo("to_int")) { // FIXME: RubyNumeric or RubyInteger instead?
            var idx: int =  RubyNumeric.fix2int(args[0]);
            
            if (idx < 0) idx += value.length();

            if (idx < 0 || idx >= value.length()) {
                throw getRuntime().newIndexError("string index out of bounds");
            }
            if (args[1] instanceof RubyFixnum) {
                modify();
                value.set(idx,  RubyNumeric.fix2int(args[1]));
            } else {
                replace(idx, 1, stringValue(args[1]));
            }
            return args[1];
        }
        if (args[0] instanceof RubyRegexp) {
            var repl: RubyString =  stringValue(args[1]);
            subpatSet(context, RubyRegexp(args[0]), 0, repl);
            return repl;
        }
        if (args[0] instanceof RubyString) {
            var orig: RubyString =  stringValue(args[0]);
            var beg: int =  value.indexOf(orig.value);
            if (beg != -1) {
                replace(beg, orig.value.length(), stringValue(args[1]));
            }
            return args[1];
        }
        if (args[0] instanceof RubyRange) {
            var idxs: Array =  RubyRange(args[0]).getBeginLength(value.length(), true, true);
            replace( idxs[0],  idxs[1], stringValue(args[1]));
            return args[1];
        }
        throw getRuntime().newTypeError("wrong argument type");
    }

    //* rb_str_slice_bang
    public  function slice_bang(context:ThreadContext, args:Array):IRubyObject{
        var argc: int =  args.length;
        var newArgs: Array =  new IRubyObject[argc + 1];
        newArgs[0] = args[0];
        if (argc > 1) newArgs[1] = args[1];

        newArgs[argc] = newString("");
        var result: IRubyObject =  op_aref(context, args);
        if (result.isNil()) return result;

        op_aset(context, newArgs);
        return result;
    }

    public  function succ():IRubyObject{
        return strDup().succ_bang();
    }

    public  function succ_bang():IRubyObject{
        if (value.length() == 0) {
            modifyCheck();
            return this;
        }

        modify();
        
        var alnumSeen: Boolean =  false;
        var pos: int =  -1;
        var c: int =  0;
        var n: int =  0;
        for ( var int:i = value.length() - 1; i >= 0; i--) {
            c = value.get(i) & 0xFF;
            if (isAlnum(c)) {
                alnumSeen = true;
                if ((isDigit(c) && c < '9') || (isLower(c) && c < 'z') || (isUpper(c) && c < 'Z')) {
                    value.set(i, (c + 1));
                    pos = -1;
                    break;
                }
                pos = i;
                n = isDigit(c) ? '1' : (isLower(c) ? 'a' : 'A');
                value.set(i, (isDigit(c) ? '0' : (isLower(c) ? 'a' : 'A')));
            }
        }
        if (!alnumSeen) {
            for ( var int:i = value.length() - 1; i >= 0; i--) {
                c = value.get(i) & 0xFF;
                if (c < 0xff) {
                    value.set(i, (c + 1));
                    pos = -1;
                    break;
                }
                pos = i;
                n = '\u0001';
                value.set(i, 0);
            }
        }
        if (pos > -1) {
            // This represents left most digit in a set of incremented
            // values?  Therefore leftmost numeric must be '1' and not '0'
            // 999 -> 1000, not 999 -> 0000.  whereas chars should be
            // zzz -> aaaa and non-alnum byte values should be "\377" -> "\001\000"
            value.insert(pos,  n);
        }
        return this;
    }

    //* rb_str_upto_m
    public  function upto(context:ThreadContext, str:IRubyObject, block:Block):IRubyObject{
        return upto(context, str, false, block);
    }

    // rb_str_upto 
    public  function upto(context:ThreadContext, str:IRubyObject, excl:Boolean, block:Block):IRubyObject{
        // alias 'this' to 'beg' for ease of comparison with MRI
        var beg: RubyString =  this;
        var end: RubyString =  stringValue(str);

        var n: int =  beg.op_cmp(end);
        if (n > 0 || (excl && n == 0)) return beg;

        var afterEnd: RubyString =  stringValue(end.succ());
        var current: RubyString =  beg;

        while (!current.equals(afterEnd)) {
            block.yield(context, current);
            if (!excl && current.equals(end)) break;

            current = RubyString(current.succ());
            
            if (excl && current.equals(end)) break;

            if (current.length().getLongValue() > end.length().getLongValue()) break;
        }

        return beg;

    }

    // rb_str_include
    public  function include_p(obj:IRubyObject):RubyBoolean{
        if (obj instanceof RubyFixnum) {
            var c: int =  RubyNumeric.fix2int(obj);
            for ( var int:i = 0; i < value.length(); i++) {
                if (value.get(i) == c) {
                    return getRuntime().getTrue();
                }
            }
            return getRuntime().getFalse();
        }
        var str: ByteList =  stringValue(obj).value;
        return getRuntime().newBoolean(value.indexOf(str) != -1);
    }

    //rb_str_to_i
    public  function to_i(args:Array):IRubyObject{
        var base: Number =  args.length == 0 ? 10 : args[0].convertToInteger().getLongValue();
        return RubyNumeric.str2inum(getRuntime(), this,  base);
    }

    //* rb_str_oct
    public  function oct():IRubyObject{
        if (isEmpty()) {
            return getRuntime().newFixnum(0);
        }

        var base: int =  8;

        var ix: int =  value.begin;

        while(ix < value.begin+value.realSize && ASCII.isSpace(value.bytes[ix] & 0xff)) {
            ix++;
        }

        var pos: int =  (value.bytes[ix] == '-' || value.bytes[ix] == '+') ? ix+1 : ix;
        if((pos+1) < value.begin+value.realSize && value.bytes[pos] == '0') {
            if(value.bytes[pos+1] == 'x' || value.bytes[pos+1] == 'X') {
                base = 16;
            } else if(value.bytes[pos+1] == 'b' || value.bytes[pos+1] == 'B') {
                base = 2;
            } else if(value.bytes[pos+1] == 'd' || value.bytes[pos+1] == 'D') {
                base = 10;
            }
        }
        return RubyNumeric.str2inum(getRuntime(), this, base);
    }

    //* rb_str_hex
    public  function hex():IRubyObject{
        return RubyNumeric.str2inum(getRuntime(), this, 16);
    }

    //* rb_str_to_f
    public  function to_f():IRubyObject{
        return RubyNumeric.str2fnum(getRuntime(), this);
    }

    //* rb_str_split_m
    public  function split(context:ThreadContext, args:Array):RubyArray{
        var i, lim:int;
        var limit:Boolean;

        if (args.length == 2) {
            lim = RubyNumeric.fix2int(args[1]);
            if (lim <= 0) {
                limit = false;
            } else {
                if (lim == 1) return value.realSize == 0 ? getRuntime().newArray() : getRuntime().newArray(this);
                limit = true;
            }
            i = 1;
        } else {
            i = 0;
            lim = 0;
            limit = false;
        }
        
        var spat: IRubyObject =  (args.length == 0 || args[0].isNil()) ? null : args[0];

        var result:RubyArray;
        if (spat == null && (spat = getRuntime().getGlobalVariables().get("$;")).isNil()) {
            result = awkSplit(limit, lim, i);
        } else {
            if (spat instanceof RubyString && RubyString(spat).value.realSize == 1) {
                var strSpat: RubyString = RubyString(spat);
                if (strSpat.value.bytes[strSpat.value.begin] == ' ') {
                    result = awkSplit(limit, lim, i);
                } else {
                    result = split(context, spat, limit, lim, i);
                }
            } else {
                result = split(context, spat, limit, lim, i);
            }
        }

        if (!limit && lim == 0) {
            while (result.size() > 0 && RubyString(result).eltInternal(result.size() - 1).value.realSize == 0)
                result.pop();
        }

        return result;
    }
    
    private  function split(context:ThreadContext, pat:IRubyObject, limit:Boolean, lim:int, i:int):RubyArray{
        var runtime: Ruby =  getRuntime();
        
        var regex: Regex =  getPattern(pat, true).getPattern();
        var beg, end, start:int;
        
        var begin: int =  value.begin;
        start = begin;
        beg = 0;
        
        var range: int =  value.begin + value.realSize;
        var matcher: Matcher =  regex.matcher(value.bytes, value.begin, range);
        
        var lastNull: Boolean =  false;
        var result: RubyArray =  runtime.newArray();
        if (regex.numberOfCaptures() == 0) { // shorter path, no captures defined, no region will be returned 
            while ((end = matcher.search(start, range, Option.NONE)) >= 0) {
                if (start == end + begin && matcher.getBegin() == matcher.getEnd()) {
                    if (value.realSize == 0) {
                        result.append(newEmptyString(runtime, getMetaClass()));
                        break;
                    } else if (lastNull) {
                        result.append(substr(beg, regex.getEncoding().length(value.bytes[begin + beg])));
                        beg = start - begin;
                    } else {
                        if (start == range) {
                            start++;
                        } else {
                            start += regex.getEncoding().length(value.bytes[start]);
                        }
                        lastNull = true;
                        continue;
                    }
                } else {
                    result.append(substr(beg, end - beg));
                    beg = matcher.getEnd();
                    start = begin + matcher.getEnd();
                }
                lastNull = false;
                if (limit && lim <= ++i) break;
            }
        } else {
            while ((end = matcher.search(start, range, Option.NONE)) >= 0) {
                var region: Region =  matcher.getRegion();
                if (start == end + begin && region.beg[0] == region.end[0]) {
                    if (value.realSize == 0) {                        
                        result.append(newEmptyString(runtime, getMetaClass()));
                        break;
                    } else if (lastNull) {
                        result.append(substr(beg, regex.getEncoding().length(value.bytes[begin + beg])));
                        beg = start - begin;
                    } else {
                        if (start == range) {
                            start++;
                        } else {
                            start += regex.getEncoding().length(value.bytes[start]);
                        }
                        lastNull = true;
                        continue;
                    }                    
                } else {
                    result.append(substr(beg, end - beg));
                    beg = start = region.end[0];
                    start += begin;
                }
                lastNull = false;
                
                for ( var int:idx =1; idx<region.numRegs; idx++) {
                    if (region.beg[idx] == -1) continue;
                    if (region.beg[idx] == region.end[idx]) {
                        result.append(newEmptyString(runtime, getMetaClass()));
                    } else {
                        result.append(substr(region.beg[idx], region.end[idx] - region.beg[idx]));
                    }
                }
                if (limit && lim <= ++i) break;
            }
        }
        
        // only this case affects backrefs 
        context.getCurrentFrame().setBackRef(runtime.getNil());
        
        if (value.realSize > 0 && (limit || value.realSize > beg || lim < 0)) {
            if (value.realSize == beg) {
                result.append(newEmptyString(runtime, getMetaClass()));
            } else {
                result.append(substr(beg, value.realSize - beg));
            }
        }
        
        return result;
    }
/*    
    private  function awkSplit(limit:Boolean, lim:int, i:int):RubyArray{
        var result: RubyArray =  getRuntime().newArray();
        
        var bytes:String = value.bytes;
        var p: int =  value.begin; 
        var endp: int =  p + value.realSize;
                    
        var skip: Boolean =  true;
        
        var end, beg:int = 0;        
        for (end = beg = 0; p < endp; p++) {
            if (skip) {
                if (ASCII.isSpace(bytes[p] & 0xff)) {
                    beg++;
                } else {
                    end = beg + 1;
                    skip = false;
                    if (limit && lim <= i) break;
                }
            } else {
                if (ASCII.isSpace(bytes[p] & 0xff)) {
                    result.append(makeShared(beg, end - beg));
                    skip = true;
                    beg = end + 1;
                    if (limit) i++;
                } else {
                    end++;
                }
            }
        }
        
        if (value.realSize > 0 && (limit || value.realSize > beg || lim < 0)) {
            if (value.realSize == beg) {
                result.append(newEmptyString(getRuntime(), getMetaClass()));
            } else {
                result.append(makeShared(beg, value.realSize - beg));
            }
        }
        return result;
    }

    //* get_pat
    private  function getPattern(obj:IRubyObject, quote:Boolean):RubyRegexp{
        if (obj instanceof RubyRegexp) {
            return RubyRegexp(obj);
        } else if (!(obj instanceof RubyString)) {
            var val: IRubyObject =  obj.checkStringType();
            if (val.isNil()) throw getRuntime().newTypeError("wrong argument type " + obj.getMetaClass() + " (expected Regexp)");
            obj = val; 
        }

        return RubyRegexp.newRegexp(getRuntime(), RubyString(obj).value, 0, quote);
    }

    //* rb_str_scan
    public  function scan(context:ThreadContext, arg:IRubyObject, block:Block):IRubyObject{
        var runtime: Ruby =  getRuntime();
        var frame: Frame =  context.getPreviousFrame();
        
        var rubyRegex: RubyRegexp =  getPattern(arg, true);
        var regex: Regex =  rubyRegex.getPattern();
        
        var range: int =  value.begin + value.realSize;
        var matcher: Matcher =  regex.matcher(value.bytes, value.begin, range);
        matcher.value = 0; // implicit start argument to scanOnce(NG)
        
        var result:IRubyObject;
        if (!block.isGiven()) {
            var ary: RubyArray =  runtime.newArray();
            
            if (regex.numberOfCaptures() == 0) {
                while ((result = scanOnceNG(rubyRegex, matcher, range)) != null) ary.append(result);
            } else {
                while ((result = scanOnce(rubyRegex, matcher, range)) != null) ary.append(result);
            }

            if (ary.size() > 0) {
                rubyRegex.updateBackRef(this, frame, matcher);
            } else {
                frame.setBackRef(runtime.getNil());
            }
            return ary;
        } else {
            var bytes:String = value.bytes;
            var size: int =  value.realSize;
            var match: RubyMatchData =  null;
            
            if (regex.numberOfCaptures() == 0) {
                while ((result = scanOnceNG(rubyRegex, matcher, range)) != null) {
                    match = rubyRegex.updateBackRef(this, frame, matcher);
                    match.use();
                    block.yield(context, result);
                    modifyCheck(bytes, size);
                }
            } else {
                while ((result = scanOnce(rubyRegex, matcher, range)) != null) {
                    match = rubyRegex.updateBackRef(this, frame, matcher);
                    match.use();
                    block.yield(context, result);
                    modifyCheck(bytes, size);
                }
            }
            frame.setBackRef(match == null ? runtime.getNil() : match);
            return this;
        }
    }

    //rb_enc_check
    private  function encodingCheck(pattern:RubyRegexp):Encoding{
        // For 1.9 compatibility, should check encoding compat between string and pattern
        return pattern.getKCode().getEncoding();
    }
    
    // no group version
    private  function scanOnceNG(regex:RubyRegexp, matcher:Matcher, range:int):IRubyObject{    
        if (matcher.search(matcher.value + value.begin, range, Option.NONE) >= 0) {
            var end: int =  matcher.getEnd();
            if (matcher.getBegin() == end) {
                if (value.realSize > end) {
                    matcher.value = end + regex.getPattern().getEncoding().length(value.bytes[value.begin + end]);
                } else {
                    matcher.value = end + 1;
                }
            } else {
                matcher.value = end;
            }
            return substr(matcher.getBegin(), end - matcher.getBegin()).infectBy(regex);
        }
        return null;
    }
    
    // group version
    private  function scanOnce(regex:RubyRegexp, matcher:Matcher, range:int):IRubyObject{    
        if (matcher.search(matcher.value + value.begin, range, Option.NONE) >= 0) {
            var region: Region =  matcher.getRegion();
            var end: int =  region.end[0];
            if (region.beg[0] == end) {
                if (value.realSize > end) {
                    matcher.value = end + regex.getPattern().getEncoding().length(value.bytes[value.begin + end]);
                } else {
                    matcher.value = end + 1;
                }
            } else {
                matcher.value = end;
            }
            
            var result: RubyArray =  getRuntime().newArray(region.numRegs);
            for ( var int:i =1; i<region.numRegs; i++) {
                var beg: int =  region.beg[i]; 
                if (beg == -1) {
                    result.append(getRuntime().getNil());
                } else {
                    result.append(substr(beg, region.end[i] - beg).infectBy(regex));
                }
            }
            return result;
        }
        return null;
    }    

    private static var SPACE_BYTELIST: ByteList =  new ByteList(ByteList.plain(" "));
    
    private function justify(args:Array, jflag:int):IRubyObject {
        var runtime: Ruby =  getRuntime();
        
        var width: int =  RubyFixnum.num2int(args[0]);
        
        var f, flen:int = 0;
        var fbuf:String;
        
        var pad:IRubyObject;

        if (args.length == 2) {
            pad = args[1].convertToString();
            var fList: ByteList =  RubyString(pad).value;
            f = fList.begin;
            flen = fList.realSize;

            if (flen == 0) throw getRuntime().newArgumentError("zero width padding");
            
            fbuf = fList.bytes;
        } else {
            f = SPACE_BYTELIST.begin;
            flen = SPACE_BYTELIST.realSize;
            fbuf = SPACE_BYTELIST.bytes;
            pad = runtime.getNil();
        }
        
        if (width < 0 || value.realSize >= width) return strDup();
        
        var res: ByteList =  new ByteList(width);
        res.realSize = width;
        
        var p: int =  res.begin;
        var pend:int;
        var pbuf: Array =  res.bytes;
        
        if (jflag != 'l') {
            var n: int =  width - value.realSize;
            pend = p + ((jflag == 'r') ? n : n / 2);
            if (flen <= 1) {
                while (p < pend) pbuf[p++] = fbuf[f];
            } else {
                var q: int =  f;
                while (p + flen <= pend) {
                    System.arraycopy(fbuf, f, pbuf, p, flen);
                    p += flen;
                }
                while (p < pend) pbuf[p++] = fbuf[q++];
            }
        }
        
        System.arraycopy(value.bytes, value.begin, pbuf, p, value.realSize);
        
        if (jflag != 'r') {
            p += value.realSize;
            pend = res.begin + width;
            if (flen <= 1) {
                while (p < pend) pbuf[p++] = fbuf[f];
            } else {
                while (p + flen <= pend) {
                    System.arraycopy(fbuf, f, pbuf, p, flen);
                    p += flen;
                }
                while (p < pend) pbuf[p++] = fbuf[f++];
            }
            
        }
        
        var resStr: RubyString =  new RubyString(runtime, getMetaClass(), res);
        resStr.infectBy(this);
        if (flen > 0) resStr.infectBy(pad);
        return resStr;
        
    }

    //* rb_str_ljust
    public function ljust(args:Array):IRubyObject {
        return justify(args, 'l');
    }

    //* rb_str_rjust
    public function rjust(args:Array):IRubyObject {
        return justify(args, 'r');
    }

    public  function center(args:Array):IRubyObject{
        return justify(args, 'c');
    }

    public  function chop():IRubyObject{
        var str: RubyString =  strDup();
        str.chop_bang();
        return str;
    }

    //* rb_str_chop_bang
    public  function chop_bang():IRubyObject{
        var end: int =  value.realSize - 1;
        if (end < 0) return getRuntime().getNil(); 

        if ((value.bytes[value.begin + end]) == '\n') {
            if (end > 0 && (value.bytes[value.begin + end - 1]) == '\r') end--;
        }

        view(0, end);
        return this;
    }

    //* rb_str_chop
    public  function chomp(args:Array):RubyString{
        var str: RubyString =  strDup();
        str.chomp_bang(args);
        return str;
    }

    //rb_str_chomp_bang
     //
     // In the common case, removes CR and LF characters in various ways depending on the value of
     //   the optional args[0].
     // If args.length==0 removes one instance of CR, CRLF or LF from the end of the string.
     // If args.length>0 and args[0] is "\n" then same behaviour as args.length==0 .
     // If args.length>0 and args[0] is "" then removes trailing multiple LF or CRLF (but no CRs at
     //   all(!)).
     // @param args See method description.
    public  function chomp_bang(args:Array):IRubyObject{
        var rsObj:IRubyObject;

        if (args.length == 0) {
            var len: int =  value.length();
            if (len == 0) return getRuntime().getNil();
            var buff:String = value.bytes;

            rsObj = getRuntime().getGlobalVariables().get("$/");

            if (rsObj == getRuntime().getGlobalVariables().getDefaultSeparator()) {
                var realSize: int =  value.realSize;
                var begin: int =  value.begin;
                if (buff[begin + len - 1] == '\n') {
                    realSize--;
                    if (realSize > 0 && buff[begin + realSize - 1] == '\r') realSize--;
                    view(0, realSize);
                } else if (buff[begin + len - 1] == '\r') {
                    realSize--;
                    view(0, realSize);
                } else {
                    modifyCheck();
                    return getRuntime().getNil();
                }
                return this;                
            }
        } else {
            rsObj = args[0];
        }

        if (rsObj.isNil()) return getRuntime().getNil();

        var rs: RubyString =  rsObj.convertToString();
        var len: int =  value.realSize;
        var begin: int =  value.begin;
        if (len == 0) return getRuntime().getNil();
        var buff:String = value.bytes;
        var rslen: int =  rs.value.realSize;

        if (rslen == 0) {
            while (len > 0 && buff[begin + len - 1] == '\n') {
                len--;
                if (len > 0 && buff[begin + len - 1] == '\r') len--;
            }
            if (len < value.realSize) {
                view(0, len);
                return this;
            }
            return getRuntime().getNil();
        }

        if (rslen > len) return getRuntime().getNil();
        var newline: byte =  rs.value.bytes[rslen - 1];

        if (rslen == 1 && newline == '\n') {
            buff = value.bytes;
            var realSize: int =  value.realSize;
            if (buff[begin + len - 1] == '\n') {
                realSize--;
                if (realSize > 0 && buff[begin + realSize - 1] == '\r') realSize--;
                view(0, realSize);
            } else if (buff[begin + len - 1] == '\r') {
                realSize--;
                view(0, realSize);
            } else {
                modifyCheck();
                return getRuntime().getNil();
            }
            return this;                
        }

        if (buff[begin + len - 1] == newline && rslen <= 1 || value.endsWith(rs.value)) {
            view(0, value.realSize - rslen);
            return this;            
        }

        return getRuntime().getNil();
    }

    //* rb_str_lstrip
    public  function lstrip():IRubyObject{
        var str: RubyString =  strDup();
        str.lstrip_bang();
        return str;
    }

    //* rb_str_lstrip_bang
    public  function lstrip_bang():IRubyObject{
        if (value.realSize == 0) return getRuntime().getNil();
        
        var i: int = 0;
        while (i < value.realSize && ASCII.isSpace(value.bytes[value.begin + i] & 0xff)) i++;
        
        if (i > 0) {
            view(i, value.realSize - i);
            return this;
        }
        
        return getRuntime().getNil();
    }

    //* rb_str_rstrip
    public  function rstrip():IRubyObject{
        var str: RubyString =  strDup();
        str.rstrip_bang();
        return str;
    }

    //* rb_str_rstrip_bang
    public  function rstrip_bang():IRubyObject{
        if (value.realSize == 0) return getRuntime().getNil();
        var i: int = value.realSize - 1;

        while (i >= 0 && value.bytes[value.begin+i] == 0) i--;
        while (i >= 0 && ASCII.isSpace(value.bytes[value.begin + i] & 0xff)) i--;

        if (i < value.realSize - 1) {
            view(0, i + 1);
            return this;
        }

        return getRuntime().getNil();
    }

    //* rb_str_strip
    public  function strip():IRubyObject{
        var str: RubyString =  strDup();
        str.strip_bang();
        return str;
        }

    //* rb_str_strip_bang
    public  function strip_bang():IRubyObject{
        var l: IRubyObject =  lstrip_bang();
        var r: IRubyObject =  rstrip_bang();

        if(l.isNil() && r.isNil()) {
            return l;
        }
        return this;
    }

    //* rb_str_count
    public  function count(args:Array):IRubyObject{
        if (args.length < 1) throw getRuntime().newArgumentError("wrong number of arguments");
        if (value.realSize == 0) return getRuntime().newFixnum(0);

        boolean[]table = new boolean[TRANS_SIZE];
        var init: Boolean =  true;
        for ( var int:i =0; i<args.length; i++) {
            var s: RubyString =  args[i].convertToString();
            s.setup_table(table, init);
            init = false;
        }

        var s: int =  value.begin;
        var send: int =  s + value.realSize;
        byte[]buf = value.bytes;
        var i: int =  0;

        while (s < send) if (table[buf[s++] & 0xff]) i++;

        return getRuntime().newFixnum(i);
    }

    //* rb_str_delete
    public  function delete(args:Array):IRubyObject{
        var str: RubyString =  strDup();
        str.delete_bang(args);
        return str;
    }

    //* rb_str_delete_bang
    public  function delete_bang(args:Array):IRubyObject{
        if (args.length < 1) throw getRuntime().newArgumentError("wrong number of arguments");
        
        var squeeze:Array = new Array;

        var init: Boolean =  true;
        for ( var int:i =0; i<args.length; i++) {
            var s: RubyString =  args[i].convertToString();
            s.setup_table(squeeze, init);
            init = false;
        }
        
        modify();
        
        if (value.realSize == 0) return getRuntime().getNil();
        var s: int =  value.begin;
        var t: int =  s;
        var send: int =  s + value.realSize;
        var buf:String = value.bytes;
        var modify: Boolean =  false;
        
        while (s < send) {
            if (squeeze[buf[s] & 0xff]) {
                modify = true;
            } else {
                buf[t++] = buf[s];
            }
            s++;
        }
        value.realSize = t - value.begin;
        
        if (modify) return this;
        return getRuntime().getNil();
    }

    //* rb_str_squeeze
    public  function squeeze(args:Array):IRubyObject{
        var str: RubyString =  strDup();
        str.squeeze_bang(args);        
        return str;        
    }

    //* rb_str_squeeze_bang
    public  function squeeze_bang(args:Array):IRubyObject{
        if (value.realSize == 0) {
            modifyCheck();
            return getRuntime().getNil();
        }

        var squeeze:Array = new Array;

        if (args.length == 0) {
            for ( var int:i =0; i<TRANS_SIZE; i++) squeeze[i] = true;
        } else {
            var init: Boolean =  true;
            for ( var int:i =0; i<args.length; i++) {
                var s: RubyString =  args[i].convertToString();
                s.setup_table(squeeze, init);
                init = false;
            }
        }

        modify();

        var s: int =  value.begin;
        var t: int =  s;
        var send: int =  s + value.realSize;
        byte[]buf = value.bytes;
        var save: int =  -1;

        while (s < send) {
            var c: int =  buf[s++] & 0xff;
            if (c != save || !squeeze[c]) buf[t++] = (save = c);
        }

        if (t - value.begin != value.realSize) { // modified
            value.realSize = t - value.begin; 
            return this;
        }

        return getRuntime().getNil();
    }

    //* rb_str_tr
    public  function tr(src:IRubyObject, repl:IRubyObject):IRubyObject{
        var str: RubyString =  strDup();
        str.tr_trans(src, repl, false);        
        return str;        
    }
    
    //* rb_str_tr_bang
    public  function tr_bang(src:IRubyObject, repl:IRubyObject):IRubyObject{
        return tr_trans(src, repl, false);
    }    
  
    private static class TR {
        int gen, now, max;
        int p, pend;
        byte[]buf;
    }

    private static var TRANS_SIZE: int =  256;
    
    //* tr_setup_table
    private void setup_table(boolean[]table, boolean init) {
        boolean[]buf = new boolean[TRANS_SIZE];
        var tr: TR =  new TR();
        var c:int;
        
        var cflag: Boolean =  false;
        
        tr.p = value.begin;
        tr.pend = value.begin + value.realSize;
        tr.buf = value.bytes;
        tr.gen = tr.now = tr.max = 0;
        
        if (value.realSize > 1 && value.bytes[value.begin] == '^') {
            cflag = true;
            tr.p++;
        }
        
        if (init) for (int i=0; i<TRANS_SIZE; i++) table[i] = true;
        
        for ( var int:i =0; i<TRANS_SIZE; i++) buf[i] = cflag;
        while ((c = trnext(tr)) >= 0) buf[c & 0xff] = !cflag;
        for ( var int:i =0; i<TRANS_SIZE; i++) table[i] = table[i] && buf[i];
    }
    
    //* tr_trans  
    private  function tr_trans(src:IRubyObject, repl:IRubyObject, sflag:Boolean):IRubyObject{
        if (value.realSize == 0) return getRuntime().getNil();
        
        var replList: ByteList =  repl.convertToString().value;
        
        if (replList.realSize == 0) return delete_bang(new IRubyObject[]{src});

        var srcList: ByteList =  src.convertToString().value;
        
        var trsrc: TR =  new TR();
        var trrepl: TR =  new TR();
        
        var cflag: Boolean =  false;
        var modify: Boolean =  false;
        
        trsrc.p = srcList.begin;
        trsrc.pend = srcList.begin + srcList.realSize;
        trsrc.buf = srcList.bytes;
        if (srcList.realSize >= 2 && srcList.bytes[srcList.begin] == '^') {
            cflag = true;
            trsrc.p++;
        }       
        
        trrepl.p = replList.begin;
        trrepl.pend = replList.begin + replList.realSize;
        trrepl.buf = replList.bytes;
        
        trsrc.gen = trrepl.gen = 0;
        trsrc.now = trrepl.now = 0;
        trsrc.max = trrepl.max = 0;
        
        var c:int;
        int[]trans = new int[TRANS_SIZE];
        if (cflag) {
            for ( var int:i =0; i<TRANS_SIZE; i++) trans[i] = 1;
            while ((c = trnext(trsrc)) >= 0) trans[c & 0xff] = -1;
            while ((c = trnext(trrepl)) >= 0); 
            for ( var int:i =0; i<TRANS_SIZE; i++) {
                if (trans[i] >= 0) trans[i] = trrepl.now;
            }
        } else {
            for ( var int:i =0; i<TRANS_SIZE; i++) trans[i] = -1;
            while ((c = trnext(trsrc)) >= 0) {
                var r: int =  trnext(trrepl);
                if (r == -1) r = trrepl.now;
                trans[c & 0xff] = r;
            }
        }
        
        modify();
        
        var s: int =  value.begin;
        var send: int =  s + value.realSize;
        byte sbuf[] = value.bytes;
        
        if (sflag) {
            var t: int =  s;
            int c0, last = -1;
            while (s < send) {
                c0 = sbuf[s++];
                if ((c = trans[c0 & 0xff]) >= 0) {
                    if (last == c) continue;
                    last = c;
                    sbuf[t++] = (c & 0xff);
                    modify = true;
                } else {
                    last = -1;
                    sbuf[t++] = c0;
                }
            }
            
            if (value.realSize > (t - value.begin)) {
                value.realSize = t - value.begin;
                modify = true;
            }
        } else {
            while (s < send) {
                if ((c = trans[sbuf[s] & 0xff]) >= 0) {
                    sbuf[s] = (c & 0xff);
                    modify = true;
                }
                s++;
            }
        }
        
        if (modify) return this;
        return getRuntime().getNil();
    }

    //* trnext  
    private  function trnext(t:TR):int{
        byte [] buf = t.buf;
        
        for (;;) {
            if (t.gen == 0) {
                if (t.p == t.pend) return -1;
                if (t.p < t.pend -1 && buf[t.p] == '\\') t.p++;
                t.now = buf[t.p++];
                if (t.p < t.pend - 1 && buf[t.p] == '-') {
                    t.p++;
                    if (t.p < t.pend) {
                        if (t.now > (buf[t.p] & 0xFF)) {
                            t.p++;
                            continue;
                        }
                        t.gen = 1;
                        t.max = buf[t.p++] & 0xFF;
                    }
                }
                return t.now & 0xff;
            } else if (++t.now < t.max) {
                return t.now & 0xff;
            } else {
                t.gen = 0;
                return t.max & 0xff;
            }
        }
    }    

    //* rb_str_tr_s
    public  function tr_s(src:IRubyObject, repl:IRubyObject):IRubyObject{
        var str: RubyString =  strDup();
        str.tr_trans(src, repl, true);        
        return str;        
    }

    //* rb_str_tr_s_bang
    public  function tr_s_bang(src:IRubyObject, repl:IRubyObject):IRubyObject{
        return tr_trans(src, repl, true);
    }

    //* rb_str_each_line
    public  function each_line(context:ThreadContext, args:Array, block:Block):IRubyObject{
        var newline:byte;
        var p: int =  value.begin;
        var pend: int =  p + value.realSize;
        var s:int;
        var ptr: int =  p;
        var len: int =  value.realSize;
        var rslen:int;
        var line:IRubyObject;
        

        var _rsep:IRubyObject;
        if (args.length == 0) {
            _rsep = getRuntime().getGlobalVariables().get("$/");
        } else {
            _rsep = args[0];
        }

        if(_rsep.isNil()) {
            block.yield(context, this);
            return this;
        }
        
        var rsep: RubyString =  stringValue(_rsep);
        var rsepValue: ByteList =  rsep.value;
        var strBytes: Array =  value.bytes;

        rslen = rsepValue.realSize;
        
        if(rslen == 0) {
            newline = '\n';
        } else {
            newline = rsepValue.bytes[rsepValue.begin + rslen-1];
        }

        s = p;
        p+=rslen;

        for(; p < pend; p++) {
            if(rslen == 0 && strBytes[p] == '\n') {
                if(strBytes[++p] != '\n') {
                    continue;
                }
                while(p < pend && strBytes[p] == '\n') {
                    p++;
                }
            }
            if(ptr<p && strBytes[p-1] == newline &&
               (rslen <= 1 || 
                ByteList.memcmp(rsepValue.bytes, rsepValue.begin, rslen, strBytes, p-rslen, rslen) == 0)) {
                line = RubyString.newStringShared(getRuntime(), getMetaClass(), this.value.makeShared(s-ptr, p-s));
                line.infectBy(this);
                block.yield(context, line);
                modifyCheck(strBytes,len);
                s = p;
            }
        }

        if(s != pend) {
            if(p > pend) {
                p = pend;
            }
            line = RubyString.newStringShared(getRuntime(), getMetaClass(), this.value.makeShared(s-ptr, p-s));
            line.infectBy(this);
            block.yield(context, line);
        }

        return this;
    }

    // rb_str_each_byte
    public  function each_byte(context:ThreadContext, block:Block):RubyString{
        var runtime: Ruby =  getRuntime();
        // Check the length every iteration, since
        // the block can modify this string.
        for ( var int:i = 0; i < value.length(); i++) {
            block.yield(context, runtime.newFixnum(value.get(i) & 0xFF));
        }
        return this;
    }

    //* rb_str_intern
    public  function intern():RubySymbol{
        var s: String =  toString();
        if (s.length() == 0) {
            throw getRuntime().newArgumentError("interning empty string");
        }
        if (s.indexOf('\0') >= 0) {
            throw getRuntime().newArgumentError("symbol string may not contain '\\0'");
        }
        return getRuntime().newSymbol(s);
    }

    public  function to_sym():RubySymbol{
        return intern();
    }

    public  function sum(args:Array):RubyInteger{
        if (args.length > 1) {
            throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 1)");
        }
        
        var bitSize: Number =  16;
        if (args.length == 1) {
            var bitSizeArg: Number =  ((RubyInteger) args[0].convertToInteger()).getLongValue();
            if (bitSizeArg > 0) {
                bitSize = bitSizeArg;
            }
        }

        var result: Number =  0;
        for ( var int:i = 0; i < value.length(); i++) {
            result += value.get(i) & 0xFF;
        }
        return getRuntime().newFixnum(bitSize == 0 ? result : result % (long) Math.pow(2, bitSize));
    }

    public static function unmarshalFrom(input:UnmarshalStream):RubyString{
        var result: RubyString =  newString(input.getRuntime(), input.unmarshalString());
        input.registerLinkTarget(result);
        return result;
    }

    //@see org.jruby.util.Pack#unpack

    public  function unpack(obj:IRubyObject):RubyArray{
        return Pack.unpack(getRuntime(), this.value, stringValue(obj).value);
    }

    //Mutator for internal string representation.
    // @param value The new java.lang.String this RubyString should encapsulate

    public  function setValue(value:CharSequence):void{
        view(ByteList.plain(value));
    }

    public  function setValue(value:ByteList):void{
        view(value);
    }

    public  function getValue():CharSequence{
        return toString();
    }

    public  function getBytes():Array{
        return value.bytes();
    }

    public  function getByteList():ByteList{
        return value;
    }

    //* used by ar-jdbc
    public  function getUnicodeValue():String{
        try {
            return new String(value.bytes,value.begin,value.realSize, "UTF8");
        } catch (e:Exception) {
            throw new RuntimeException("Something's seriously broken with encodings", e);
        }
    }   
*/ 
}

}